Payload CMS + Next.js, Done Right
Skip the configuration guesswork. Set up Payload CMS inside your Next.js project with the right folder structure, admin panel, and database choices so your backend runs locally on the first try.

If you want a backend that feels like you’re still writing React but gives you a full admin panel, authentication, and a database API out of the box, you use Payload CMS inside your Next.js app. That’s the whole pitch. Not as a separate service. Not connected via HTTP from another VPS. It runs in your own /app folder. This guide gets you from npx create-payload-app to a working admin UI, with the database decisions and deployment traps I wish someone had flagged for me when I first paired the two.
How to Use Payload CMS Inside an Existing Next.js Project
The official scaffold is the safest starting point, even when you already have a Next.js app. Run the create command inside your project root, and it negotiates the merge.
npx create-payload-app@latestThe CLI asks three questions that matter:
- Name / folder: accept the current directory name.
- Add to existing Next.js app?: select “Yes”. Payload will splice itself into your project without overwriting your pages.
- Database: pick
MongoDB(local or Atlas) orPostgres(with Drizzle ORM). I lean toward Postgres for relational data because Payload 3.0’s Drizzle adapter treats it as a first-class citizen.
After the CLI finishes, you’ll see new directories:
payload/— the CMS config, collections, and any custom components.payload-types.ts— auto-generated TypeScript types mirroring your collection schemas. This ends arguments about the shape of API responses.
The installer also does something subtle: it updates your tsconfig.json paths alias so @payload-config points to your CMS entry. That single alias is why imports stay clean across server and client boundaries.
Manual Installation When You Want Full Control
If the merge wizard makes you nervous, install by hand. The Payload docs list the steps, but I’ll compress them to what actually matters:
npm install payload @payloadcms/next @payloadcms/db-mongodb \
@payloadcms/richtext-lexical sharpThen create payload.config.ts at the project root:
import { buildConfig } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
export default buildConfig({
collections: [],
globals: [],
editor: lexicalEditor(),
db: mongooseAdapter({
url: process.env.DATABASE_URI || 'mongodb://localhost/payload',
}),
secret: process.env.PAYLOAD_SECRET || 'dev-secret-never-use-in-prod',
})Two files wire it into Next.js.
app/(payload)/admin/[[...slug]]/page.tsx:
import { RootPage } from '@payloadcms/next/views'
import config from '@payload-config'
const Page = () => <RootPage config={config} />
export default Pageapp/(payload)/api/[[...slug]]/route.ts:
import { REST } from '@payloadcms/next/routes'
import config from '@payload-config'
export const { GET, POST, PATCH, DELETE } = REST(config)That’s it. next dev now serves your admin UI at /admin and a REST (plus GraphQL) API at /api. No separate server process, no second port.
Pick the Database That Matches Your Hosting Plan
Payload needs a persistent database, and this decision affects whether you can deploy on Vercel at all.
Database | Adapter Package | Best Fit |
|---|---|---|
MongoDB | | Quick prototyping, existing Atlas cluster |
Postgres (Drizzle) | | Relational data, SQL shops, PlanetScale-compatible migrations |
SQLite (Turso/LibSQL) | | Edge-light projects, embedded apps |
Vercel serverless functions disconnect from the database between cold starts, which means MongoDB Atlas connection pooling works fine, but self-hosted Postgres on a VPS from our web services stack gives you fewer timeout surprises. If you deploy anywhere outside Vercel — a Docker container on Hetzner, a Railway instance, or Fly.io — every database above works without extra gymnastics.
Build Your First Collection (and Why the Admin Panel Reacts Like a Component)
Payload collections are the equivalent of a database table plus its admin UI. Define one in payload/collections/Posts.ts:
import { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'content', type: 'richText' },
{ name: 'publishedDate', type: 'date', admin: { position: 'sidebar' } },
],
}Register it in payload.config.ts inside the collections array, and the admin panel instantly shows a new sidebar entry labeled “Posts.” This isn’t a separate build step — Payload reads your config at runtime and renders the React admin UI directly. That means any custom field component you write is just a React component, not a plugin or extension in a foreign templating language.
When you create a post in the admin panel, data flows to your database. Retrieve it from any server component or route handler:
import config from '@payload-config'
import { getPayload } from 'payload'
const payload = await getPayload({ config })
const posts = await payload.find({ collection: 'posts', limit: 10 })The payload-types.ts file generated alongside your config ensures posts.docs is typed down to the field level. No more guessing whether publishedDate is a string or a Date object.
Keep Payload Fast When You Self-Host
A recent self-hosted deployment walkthrough identifies a sharp edge: Payload and Next.js share a Node process, so the CMS admin panel and your public frontend compete for resources under heavy traffic. When self-hosting, do three things:
- Set
sharpas the image processor. Payload falls back to a slower pure-JS library otherwise.sharpis native code, and sharp images make the admin media browser feel fast. - Increase Node memory:
NODE_OPTIONS=--max-old-space-size=4096prevents the admin UI from crashing during bulk uploads. - Run the Payload admin on a subdomain with a reverse proxy (Nginx or Caddy) if you expect moderate admin traffic alongside a public site. This keeps the
/adminroute group isolated at the server level.
How We Fit Into This Stack
At Techpotions, we build web applications where the content layer is tightly coupled to AI features — in-app notifications driven by CMS content, context-aware internal tools, editable prompt templates that non-developers manage. Payload becomes the interface between engineers and domain experts. Our web service hands you that setup: Payload configured inside Next.js, database wired for your deployment target, custom collection blueprints that match your data model rather than a generic blog schema, and admin components that feel native to your team’s workflow. If that sounds closer to what you need than a blank scaffold, let’s talk.
FAQ
Will Payload CMS slow down my Next.js frontend?
Payload only runs on the server. The client bundle does not include the CMS, and the admin panel is code-split under the /(payload) route group. Your public pages don’t ship a single byte of admin JavaScript — only the API client you consciously import.
Can I use Payload with the Next.js App Router and Server Components?
Yes. Payload 3.0 leverages the App Router natively. You query collections directly inside React Server Components with getPayload (as shown above), and you never expose a REST endpoint unless you choose to.
Do I have to self-host, or can I deploy on Vercel?
You can deploy on Vercel with a serverless-compatible database like MongoDB Atlas or Turso. But Payload’s admin UI opens a long-lived WebSocket for live preview, which Vercel’s serverless model drops eventually. For production, a containerized deployment on Railway, Fly.io, or a plain VPS matches Payload’s runtime better.