Building a Modern Headless CMS with Payload 3.0, Next.js 15 & Supabase
A deep dive into setting up Payload CMS 3.0 within Next.js 15 App Router, paired with Supabase PostgreSQL and S3 Storage for a scalable headless architecture.
Modern web development demands agility, type safety, and seamless content delivery. Traditional monolithic CMS architectures often introduce unnecessary overhead, slow build times, and decoupled typing issues. With the release of Payload CMS 3.0, the paradigm shifts dramatically: Payload now runs natively inside Next.js App Router as a standard route handler, eliminating the need for a separate Node.js server.
In this guide, we explore how I engineered Lana CMS using Payload 3.0, Next.js 15, and Supabase PostgreSQL.
Why Payload 3.0?
- Native Next.js 15 Integration: Operates within your existing Next.js application without extra infrastructure.
- End-to-End Type Safety: Automatically generates TypeScript interfaces directly from your collection schemas.
- Lexical Rich Text Editor: Highly extensible, lightweight rich text editor powered by Meta's Lexical framework.
- Database Agnostic: Native support for PostgreSQL with
@payloadcms/db-postgresvia Drizzle ORM under the hood.
┌────────────────────────────────────────────────────────┐
│ Next.js 15 App │
│ │
│ ┌───────────────────────┐ ┌─────────────────────┐ │
│ │ /admin (Payload UI) │ │ /api/projects REST │ │
│ └───────────┬───────────┘ └──────────┬──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Payload Local API & Hooks │ │
│ └──────────────────────────┬───────────────────────┘ │
└─────────────────────────────┼──────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Supabase PostgreSQL │
│ & S3 Compatible Media Storage │
└─────────────────────────────────────┘Core Architecture Setup
1. Payload Configuration (payload.config.ts)
Here is how our central Payload configuration connects to Supabase PostgreSQL with transaction pooling:
import { postgresAdapter } from "@payloadcms/db-postgres";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
import { s3Storage } from "@payloadcms/storage-s3";
import { buildConfig } from "payload";
import { Projects } from "./collections/Projects";
import { Posts } from "./collections/Posts";
import { Media } from "./collections/Media";
export default buildConfig({
admin: {
user: "users",
importMap: { baseDir: path.resolve(dirname) },
},
collections: [Projects, Posts, Media],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || "",
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URL || "",
},
push: false, // Use migrations for production stability
}),
plugins: [
s3Storage({
collections: {
media: true,
},
config: {
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION || "ap-southeast-1",
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
},
},
bucket: process.env.S3_BUCKET || "lana-cms-media",
}),
],
});Managing Multi-Language Content
For a portfolio and technical blog, localization is paramount. Payload makes localization frictionless by setting localized: true on schema fields:
export const Posts: CollectionConfig = {
slug: "posts",
fields: [
{
name: "title",
type: "text",
required: true,
localized: true,
},
{
name: "slug",
type: "text",
required: true,
unique: true,
index: true,
},
{
name: "content",
type: "richText",
required: true,
localized: true,
}
]
};Performance Results & Key Takeaways
By leveraging Payload 3.0 inside Next.js 15:
- Zero CORS latency for server-side fetches.
- Server Actions & ISR Support: Automatic revalidation with
revalidateTag('cms-posts')whenever content updates. - Lighthouse Performance Score: 100/100 on both mobile and desktop.
Conclusion
Payload 3.0 bridges the gap between developer experience and content editor ergonomics. Pairing it with Supabase provides enterprise reliability at zero upfront server costs.