Best CMS Solutions for Next.js in 2026
TL;DR
Sanity for developer-first flexibility; Contentful for enterprise; Payload for self-hosted TypeScript control. Sanity (~600K weekly downloads) is the developer's CMS — real-time collaboration, GROQ query language, and React-based studio. Contentful (~400K) is the established enterprise choice. Payload CMS (~200K, fast-growing) is TypeScript-native, self-hosted, and generates its own admin UI from your schema — zero vendor lock-in. Keystatic is the emerging file-based option that stores content in your Git repository.
Key Takeaways
- Sanity: ~600K weekly downloads — GROQ queries, real-time, React studio, free tier
- Contentful: ~400K downloads — enterprise feature set, established market leader
- Payload CMS: ~200K downloads — self-hosted, TypeScript-first, admin UI auto-generated
- Keystatic — Git-backed CMS, no database needed, content stored in your repo
- Hygraph (formerly GraphCMS) — GraphQL-native, strong federation
How to Choose a CMS for Next.js
The right headless CMS for a Next.js project depends on three factors more than anything else: who edits content, who owns infrastructure, and how complex your content model is.
If your editorial team needs a polished interface and real-time collaboration, Sanity or Contentful are the right choices — both have mature admin experiences built for non-technical editors. If you are a developer-only team building an internal tool or a startup MVP and you want to avoid vendor lock-in, Payload CMS gives you everything in your own codebase. If your content is mostly blog posts and documentation and you want zero runtime cost, Keystatic stores everything in Git and requires no database.
The Next.js App Router (introduced in Next.js 13 and stabilized in 14–15) changed how CMS data fetching works. Most modern CMS SDKs now have first-class support for React Server Components — you fetch data in an async server component without useEffect or client-side loading states. The examples below use this pattern throughout.
Package Health Table
| CMS | Weekly Downloads | Hosting | Free Tier | TypeScript |
|---|---|---|---|---|
@sanity/client | ~600K | Managed (Sanity Cloud) | 3 users, 5GB | Yes |
contentful | ~400K | Managed (Contentful Cloud) | 25K records | Yes (CLI) |
payload | ~200K | Self-hosted | Free | Yes, native |
@keystatic/core | ~50K | Git / self-hosted | Free | Yes |
Sanity + Next.js
Sanity is the CMS most beloved by developers in 2026. The GROQ query language is expressive in a way that SQL and GraphQL are not — the join syntax (author->{ name }) is particularly elegant compared to multi-step REST fetching. The Sanity Studio is built entirely in React, which means you can customize it with your own React components when your content model needs unusual interfaces.
The next-sanity package provides utilities specifically designed for Next.js App Router: a client configured for both published and draft content, a SanityImage component for optimized image rendering, and integration with Next.js Draft Mode for content preview.
// Sanity — schema definition
// schemas/post.ts
export default {
name: 'post',
title: 'Blog Post',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
validation: (Rule) => Rule.required(),
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
options: { source: 'title' },
},
{
name: 'content',
title: 'Content',
type: 'array',
of: [
{ type: 'block' }, // Portable Text
{ type: 'image' },
{ type: 'code' }, // code-input plugin
],
},
{
name: 'publishedAt',
type: 'datetime',
},
],
};
// Sanity — GROQ queries in Next.js App Router
import { createClient } from 'next-sanity';
import { groq } from 'next-sanity';
const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
apiVersion: '2024-01-01',
useCdn: true,
});
// GROQ — Sanity's query language
const POSTS_QUERY = groq`
*[_type == "post" && !(_id in path('drafts.**'))] | order(publishedAt desc) {
_id,
title,
"slug": slug.current,
publishedAt,
"author": author->{ name, "image": image.asset->url },
"coverImage": mainImage.asset->url,
excerpt,
}
`;
// Fetch in Next.js App Router — runs on the server, no useEffect needed
async function getPosts() {
return client.fetch(POSTS_QUERY, {}, { next: { tags: ['posts'] } });
}
// app/blog/page.tsx
export default async function BlogPage() {
const posts = await getPosts();
return (
<div>
{posts.map(post => (
<article key={post._id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
// Sanity — live preview with Draft Mode
// app/api/draft/route.ts
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const secret = searchParams.get('secret');
if (secret !== process.env.SANITY_PREVIEW_SECRET) {
return new Response('Invalid token', { status: 401 });
}
draftMode().enable();
redirect(searchParams.get('slug') ?? '/');
}
Sanity's free tier includes 3 users, 5GB assets, and 10,000 API requests per day — plenty for small projects. Paid plans start around $99/month for larger teams. The Content Lake API means all content is stored in Sanity's cloud infrastructure; there is no self-hosted option.
Contentful + Next.js
Contentful is the CMS that enterprises standardized on during 2015–2022, and many large organizations still run it today. Its strength is a mature content modeling interface, strong field validation, extensive webhooks, and a long track record of reliability at scale.
The SDK provides both REST and GraphQL APIs. The REST API is straightforward and well-documented; the GraphQL API is particularly useful when you have complex content models with many references and want to fetch exactly the fields you need in one request.
// Contentful — typed content fetching
import contentful from 'contentful';
const client = contentful.createClient({
space: process.env.CONTENTFUL_SPACE_ID!,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
// For preview:
// host: 'preview.contentful.com',
// accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
});
// TypeScript types from Contentful CLI
import type { TypeBlogPost } from '@/contentful/types';
async function getBlogPosts(): Promise<TypeBlogPost[]> {
const entries = await client.getEntries<TypeBlogPost>({
content_type: 'blogPost',
order: ['-fields.publishDate'],
limit: 10,
'fields.publishDate[lte]': new Date().toISOString(),
});
return entries.items;
}
Contentful's TypeScript type generation (contentful-typescript-codegen or the newer CLI tool) is genuinely excellent — it reads your content model and generates TypeScript interfaces for all your content types. This means your fetched data is fully typed without any manual work.
// Contentful — webhook revalidation (Next.js ISR)
// app/api/contentful-webhook/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(req: Request) {
const secret = req.headers.get('x-contentful-webhook-secret');
if (secret !== process.env.CONTENTFUL_WEBHOOK_SECRET) {
return new Response('Unauthorized', { status: 401 });
}
const body = await req.json();
const contentType = body.sys.contentType?.sys.id;
if (contentType === 'blogPost') {
revalidateTag('blog-posts');
revalidatePath('/blog');
}
return new Response('OK');
}
The pricing is the main concern with Contentful. The free tier is generous (25,000 records, 2 locales, 5 users), but the jump to a paid plan starts at $300/month for teams that need more than the free tier allows. For many startups, this is a significant cost relative to self-hosted alternatives.
Payload CMS (Self-Hosted)
Payload is the TypeScript-native CMS that has grown explosively since its v2 release. The core idea is radical: you define your content model in TypeScript, and Payload generates the admin UI, REST API, GraphQL API, and TypeScript types automatically from that definition. No schema migrations, no generated code to commit — everything derives from your collection config.
Payload v3 (released in late 2024) embedded itself inside Next.js rather than running as a separate server. Your Payload instance runs inside your Next.js app — the admin panel is served at /admin, the API is served from Next.js Route Handlers, and data fetching in React Server Components can call Payload's local API directly without HTTP overhead.
// Payload — collection schema (TypeScript-first)
// collections/Posts.ts
import type { CollectionConfig } from 'payload';
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
defaultColumns: ['title', 'author', 'status', 'publishedAt'],
},
access: {
read: () => true, // Public
create: isAdmin,
update: isAdmin,
delete: isAdmin,
},
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', unique: true, required: true },
{
name: 'content',
type: 'richText', // Lexical rich text editor
},
{
name: 'status',
type: 'select',
options: ['draft', 'published'],
defaultValue: 'draft',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
{ name: 'publishedAt', type: 'date' },
],
hooks: {
beforeChange: [
({ data }) => {
if (data.status === 'published' && !data.publishedAt) {
data.publishedAt = new Date().toISOString();
}
return data;
},
],
},
};
// payload.config.ts — main Payload configuration
import { buildConfig } from 'payload';
import { Posts } from './collections/Posts';
import { Users } from './collections/Users';
import { postgresAdapter } from '@payloadcms/db-postgres';
export default buildConfig({
collections: [Posts, Users],
secret: process.env.PAYLOAD_SECRET!,
db: postgresAdapter({
pool: { connectionString: process.env.DATABASE_URL },
}),
admin: {
user: 'users',
},
});
// Next.js RSC — querying Payload's local API (no HTTP):
import { getPayload } from 'payload';
import config from '@/payload.config';
export default async function BlogPage() {
const payload = await getPayload({ config });
const { docs: posts } = await payload.find({
collection: 'posts',
where: { status: { equals: 'published' } },
sort: '-publishedAt',
});
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
Because Payload generates TypeScript types from your collection config, the posts array above is fully typed — including all your custom fields — with zero manual type definitions. This is a significant advantage for teams that want type safety throughout their content layer.
The self-hosted nature means you bring your own database (PostgreSQL or MongoDB) and your own hosting. Vercel + Neon Postgres is a common production stack for Payload v3.
Keystatic (Git-Backed)
Keystatic stores content in your Git repository as YAML, JSON, or MDX files. There is no database, no external service, and no runtime cost. Content editors use a web UI that reads from and writes to your GitHub repository via the GitHub API.
This approach is ideal for content-heavy sites where the content model is relatively simple (blog posts, documentation, landing page copy) and the team is comfortable with Git-based workflows. Every content change is a Git commit, giving you a full audit trail and the ability to roll back to any previous state.
// Keystatic — content stored in your Git repo (no database)
// keystatic.config.ts
import { config, collection, fields } from '@keystatic/core';
export default config({
storage: {
kind: 'github', // 'local' for dev, 'github' for prod
repo: 'myorg/mysite',
},
collections: {
posts: collection({
label: 'Blog Posts',
slugField: 'title',
path: 'content/blog/*',
format: { contentField: 'content' },
schema: {
title: fields.slug({ name: { label: 'Title' } }),
publishedAt: fields.date({ label: 'Published At' }),
summary: fields.text({ label: 'Summary', multiline: true }),
content: fields.markdoc({ label: 'Content' }),
},
}),
},
});
Because the content is files in your repo, you read it with standard file system operations or Next.js's native MDX support — no API calls, no SDK, no network latency. This also means the content is available during the build step without any external dependencies, making it very resilient.
The limitation is that Keystatic is not suited for complex relational content (blog posts with authors who have profiles, nested content, user-generated content) or content that changes frequently. For a marketing site or a technical blog, it is hard to beat.
Comparison Table
| CMS | Type | Free Tier | Self-Hosted | Next.js DX | TypeScript |
|---|---|---|---|---|---|
| Sanity | Managed | 3 users, 5GB | No | Excellent | Yes |
| Contentful | Managed | 25K records | No | Good | Yes (CLI) |
| Payload | Self-hosted | Free | Yes | Excellent (v3) | Yes, native |
| Keystatic | Git-backed | Free | Yes | Good | Yes |
| Strapi | Self-hosted | Free | Yes | Good | Yes |
When to Choose
Sanity is the right choice for startups and development teams who want a polished editorial experience without enterprise pricing. GROQ is genuinely more expressive than SQL or GraphQL for content queries, and the React-based studio means you can build custom field types when needed.
Contentful makes sense when enterprise requirements drive the decision: SOC 2 compliance, SLAs, an established legal relationship, or an existing Contentful instance that the rest of the organization uses. The pricing is significant but justified at scale.
Payload CMS is the clear choice when you want zero vendor lock-in, need TypeScript types auto-generated from your schema, or are building a product where content management is deeply integrated with application logic (user-generated content, multi-tenant systems, complex access control).
Keystatic is ideal for documentation sites, blogs, and marketing sites where content is primarily flat files, the team is developer-led, and zero runtime cost is a priority.
Content Modeling Considerations
The shape of your content is as important as the CMS you choose, and different CMS architectures handle different content shapes better. The primary distinction is between flat content (blog posts, landing page copy, product descriptions — documents that stand alone without much relational structure), relational content (articles with authors who have profiles, courses with lessons that belong to modules that belong to tracks — a graph of linked documents), and tree content (documentation with a hierarchical sidebar structure, nested page hierarchies).
Sanity handles relational content better than any other CMS on this list. GROQ's join syntax — author->{ name, bio, "image": image.asset->url } — fetches referenced documents in a single query, without the N+1 problem that plagues REST-based CMS APIs. Contentful's content model is also strongly relational, with explicit reference fields and link resolution in the SDK, but the GraphQL API is more natural for complex queries than the REST API. For deeply relational content (user-generated content with profiles, comments, and reactions; e-commerce with products, variants, and inventory), Payload CMS is the strongest choice because you can write actual database queries against your content, not just CMS API calls.
Tree content — hierarchical documentation structures — is where file-based systems shine and traditional headless CMSes struggle. Keystatic and similar Git-backed tools naturally model a folder hierarchy as a content tree, because the file system itself is the hierarchy. Sanity and Contentful require building explicit "parent" reference fields or tree structures that editors must maintain manually. For documentation sites where the content tree is the product, evaluate Nextra, Fumadocs, or Docusaurus alongside CMS-backed options — these documentation frameworks handle tree content as a first-class concept.
Self-Hosting vs Managed: Cost and Data Residency
The managed vs self-hosted decision is fundamentally a question of what overhead you are willing to accept. Managed CMS services (Sanity, Contentful) eliminate infrastructure management: no database to provision, back up, or scale; no CDN configuration; no uptime responsibility. The cost is a monthly subscription and data residency constraints. Sanity and Contentful both store content in their cloud infrastructure, which means you cannot control which geographic region your content data lives in at the per-project level on lower pricing tiers. For GDPR-sensitive applications that must store data in specific EU regions, this is a concrete concern that must be resolved before committing to a managed CMS.
Payload CMS and Keystatic are self-hosted by default, which inverts the trade-off. You pay in infrastructure complexity (provisioning PostgreSQL, managing connection pooling, handling database backups) rather than in subscription fees. At small scale, the managed infrastructure overhead is minimal — Vercel + Neon Postgres handles most of it automatically. At large scale, infrastructure costs can exceed managed CMS subscription costs, so the comparison requires actual cost modeling with real traffic and content volume numbers rather than rule-of-thumb assumptions.
The break-even analysis changes significantly at scale. Contentful's pricing jumps from free (25K records) to $300/month for the next tier. Payload on a dedicated Postgres instance with daily backups might cost $50-100/month from a cloud provider. At low content volume, the cost difference is clear. At high content volume — hundreds of thousands of records, millions of API requests — Sanity and Contentful's usage-based pricing can become substantial, while self-hosted Payload costs grow with compute rather than content operations. Teams projecting significant growth should model both paths at their 12-month and 36-month content and traffic projections before committing.
Related Reading
- Compare Sanity and Contentful package metrics: /compare/sanity-vs-contentful
- Payload CMS package health: /packages/payload
- Best static site generators for content sites: /blog/best-static-site-generators-2026
See the live comparison
View sanity vs. contentful on PkgPulse →