Set Up Payload CMS with Next.js: Complete Integration Guide
Payload CMS has revolutionized headless content management by offering seamless Next.js integration. In this comprehensive guide, we’ll walk through setting up Payload CMS with Next.js from scratch, creating a powerful full-stack application.
Prerequisites
- Node.js 18+ installed
- Basic knowledge of React and Next.js
- MongoDB database (local or cloud)
Step 1: Initialize Your Next.js Project
Start by creating a new Next.js application with TypeScript support:
npx create-next-app@latest my-payload-app --typescript --tailwind --eslint
cd my-payload-app
Step 2: Install Payload CMS
Install Payload CMS and its dependencies:
npm install payload @payloadcms/bundler-webpack @payloadcms/db-mongodb @payloadcms/richtext-slate
npm install --save-dev @types/node
Step 3: Configure Payload
Create a payload.config.ts file in your project root:
import { buildConfig } from 'payload/config'
import { webpackBundler } from '@payloadcms/bundler-webpack'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { slateEditor } from '@payloadcms/richtext-slate'
export default buildConfig({
admin: {
user: 'users',
bundler: webpackBundler(),
},
editor: slateEditor({}),
collections: [
{
slug: 'users',
auth: true,
fields: [
{
name: 'name',
type: 'text',
required: true,
},
],
},
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
},
],
},
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
},
db: mongooseAdapter({
url: process.env.DATABASE_URI,
}),
})
const path = require('path')
Step 4: Set Up Environment Variables
Create a .env.local file with your configuration:
DATABASE_URI=mongodb://localhost:27017/payload-nextjs
PAYLOAD_SECRET=your-secret-key-here
NEXTAUTH_SECRET=your-nextauth-secret
Step 5: Create API Routes
Create pages/api/[...payload].ts for Payload’s API routes:
import { NextApiRequest, NextApiResponse } from 'next'
import payload from 'payload'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.DATABASE_URI,
express: app,
onInit: () => {
payload.logger.info('Payload Admin initialized')
},
})
return payload.handler(req, res)
}
export default handler
Step 6: Initialize Payload in Next.js
Create a lib/payload.ts file to initialize Payload:
import payload from 'payload'
if (!process.env.PAYLOAD_SECRET) {
throw new Error('PAYLOAD_SECRET environment variable is missing')
}
let cached = (global as any).payload
if (!cached) {
cached = (global as any).payload = { client: null, promise: null }
}
export const getPayloadClient = async () => {
if (cached.client) {
return cached.client
}
if (!cached.promise) {
cached.promise = payload.init({
secret: process.env.PAYLOAD_SECRET!,
local: true,
})
}
try {
cached.client = await cached.promise
} catch (e: unknown) {
cached.promise = null
throw e
}
return cached.client
}
Step 7: Create a Blog Page
Create pages/blog/[slug].tsx to display blog posts:
import { GetStaticProps, GetStaticPaths } from 'next'
import { getPayloadClient } from '../../lib/payload'
interface Post {
id: string
title: string
content: any
slug: string
}
interface BlogPostProps {
post: Post
}
const BlogPost = ({ post }: BlogPostProps) => {
return (
{post.title}
{/* Render rich text content */}
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const payload = await getPayloadClient()
const posts = await payload.find({
collection: 'posts',
limit: 1000,
})
return {
paths: posts.docs.map((post) => ({
params: { slug: post.slug },
})),
fallback: 'blocking',
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const payload = await getPayloadClient()
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: params?.slug,
},
},
})
if (!posts.docs[0]) {
return {
notFound: true,
}
}
return {
props: {
post: posts.docs[0],
},
revalidate: 60,
}
}
export default BlogPost
Step 8: Update Package.json Scripts
Add Payload-specific scripts to your package.json:
{
"scripts": {
"dev": "cross-env PAYLOAD_CONFIG_PATH=payload.config.ts next dev",
"build": "cross-env PAYLOAD_CONFIG_PATH=payload.config.ts next build",
"start": "cross-env PAYLOAD_CONFIG_PATH=payload.config.ts next start",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=payload.config.ts payload generate:types"
}
}
Step 9: Start Development
Install cross-env and start your development server:
npm install --save-dev cross-env
npm run dev
Step 10: Access Payload Admin
Navigate to http://localhost:3000/admin to access the Payload admin panel and create your first user account.
Best Practices
- Type Safety: Always generate TypeScript types with
npm run generate:types - Environment Security: Never commit sensitive environment variables
- Database Indexing: Add proper indexes for frequently queried fields
- Caching: Implement proper caching strategies for production
- Validation: Use Payload’s built-in validation for data integrity
Troubleshooting Common Issues
- MongoDB Connection: Ensure your MongoDB instance is running and accessible
- Port Conflicts: Check if port 3000 is available or configure a different port
- TypeScript Errors: Run
npm run generate:typesafter config changes
Key Takeaway: Payload CMS with Next.js creates a powerful, type-safe full-stack application. The seamless integration eliminates traditional headless CMS complexity while providing enterprise-grade features.
Hashtags: #PayloadCMS #NextJS #HeadlessCMS #FullStack #TypeScript #WebDevelopment #CMS
Resources: