How to Set Up Payload CMS with Next.js: Complete Integration Guide

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:types after 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: