blog · side by side
Payload blog vs Sanity blog for a Next.js app
Both fill the blog slot, so a generated repo carries one or the other, never both. Every line below is read out of the two manifests.
Short answer
Pick Payload blog if
Teams who want editors in an admin UI but will not put their content in someone else's database. Strong when CMS content joins application data. Also when you need custom fields, hooks or access rules a hosted CMS will not give you. Or when data residency and export matter.
Pick Sanity blog if
Teams where the people writing are not the people deploying: marketing, content and design working next to engineering. Best when content is structured (authors, categories, references between documents), not a pile of long markdown files. Also when you need previews, roles and a real-time editor without building one.
Side by side
Price, obligations, and the surface each one adds. No row is written by hand. This is manifest.yaml, rendered.
| From the manifest | Option APayload blog | Option BSanity blog |
|---|---|---|
| In one line | Payload blog A full CMS in your own repo and database. No second service, no content API bill. | Sanity blog A real editorial CMS your writers can use, with the schema still living in your repo. |
| Pricing | Payload blog Free and open source (MIT). You host it, so the cost is the Postgres and Vercel functions you already pay for. Payload also sells an Enterprise plan with dedicated support and SSO. It has no public price. | Sanity blog Free plan: up to 20 seats, 2 public datasets and 10k documents. Growth is $15 per seat per month: up to 50 seats, private datasets and 25k documents. Past its included quotas, Growth bills pay-as-you-go. Enterprise is custom. |
| Best for | Payload blog Teams who want editors in an admin UI but will not put their content in someone else's database. Strong when CMS content joins application data. Also when you need custom fields, hooks or access rules a hosted CMS will not give you. Or when data residency and export matter. | Sanity blog Teams where the people writing are not the people deploying: marketing, content and design working next to engineering. Best when content is structured (authors, categories, references between documents), not a pile of long markdown files. Also when you need previews, roles and a real-time editor without building one. |
| Trade-offsVerbatim from the manifest | Payload blog
| Sanity blog
|
| Required companionsAdded for you, with a reason | Payload blog
| Sanity blog Nothing. It stands on its own. |
| Recommended alongsideSuggested, never added for you | Payload blog
| Sanity blog Nothing suggested. |
| Env vars you will manageEvery one documented in docs/onboard.md | Payload blog 1 variable · 1 required
| Sanity blog 4 variables · 4 required
|
| Dependencies added | Payload blog
| Sanity blog
|
| MCP serversWritten into .mcp.json | Payload blog None. No extra agent tools from this one. | Sanity blog None. No extra agent tools from this one. |
| Footprint in your repo | Payload blog 17 files, plus 5 injections into shared stack files | Sanity blog 21 files, plus 5 injections into shared stack files |
What changes in your repo
The paths each battery contributes, diffed. A path in the third list is written by both, so swapping rewrites that file rather than adding one.
Only with Payload blog (17)
src/16 files
app/9 files
(frontend)/3 files
api/1 file
blog-preview/1 file
- route.ts
blog/2 files
[slug]/1 file
- page.tsx
- page.tsx
(payload)/6 files
cms/3 files
[[...segments]]/2 files
- not-found.tsx
- page.tsx
- importMap.ts
cms-api/2 files
[...slug]/1 file
- route.ts
graphql/1 file
- route.ts
- layout.tsx
components/1 file
cms/1 file
- rich-text.tsx
payload/6 files
collections/3 files
- media.ts
- posts.ts
- users.ts
migrations/1 file
- index.ts
- access.ts
- local.ts
- payload.config.ts
Only with Sanity blog (21)
sanity/10 files
lib/5 files
- client.ts
- fetch.ts
- image.ts
- queries.ts
- types.ts
schemas/4 files
- author.ts
- block-content.ts
- index.ts
- post.ts
- env.ts
src/9 files
app/6 files
(sanity)/2 files
blog/2 files
[slug]/1 file
- page.tsx
- page.tsx
api/3 files
draft-mode/2 files
disable/1 file
- route.ts
enable/1 file
- route.ts
sanity/1 file
revalidate/1 file
- route.ts
studio/1 file
[[...tool]]/1 file
- page.tsx
components/3 files
sanity/3 files
- draft-banner.tsx
- portable-text.tsx
- sanity-image.tsx
- sanity.cli.ts
- sanity.config.ts
Shared stack files Payload blog injects into
- bare-route-groups
- env-required
- nav-links
- next-config-wrappers
- verify-checks
Shared stack files Sanity blog injects into
- bare-route-groups
- env-required
- legal-processors
- nav-links
- verify-checks
Payload blog in your .env.local
# required
PAYLOAD_SECRET=replace-with-32-random-characters
Sanity blog in your .env.local
# required
NEXT_PUBLIC_SANITY_DATASET=production
NEXT_PUBLIC_SANITY_PROJECT_ID=abc12xyz
SANITY_API_READ_TOKEN=sk_replace_me
SANITY_REVALIDATE_SECRET=replace-with-32-random-characters
What changes for your agents
Each battery ships rules, skills, subagents and hooks that an agent loads before it touches the code that battery owns. Picking one is also picking how your agents behave in payload.config.ts.
Payload blog
2
Skills
2
Rules
5
Solution docs
Rules (2)
Read Payload through the local API, never over HTTP
src/app/(frontend)/** · src/app/(payload)/** · src/payload/local.ts · src/components/cms/**
Collections are code, and every change ships with a migration
payload.config.ts · src/payload/**
Skills (2)
/add-collection
Add a Payload collection end to end: fields, explicit access control, migration, regenerated types and the page that renders it.
/payload-migrate
Create, review, apply and deploy Payload migrations safely, including renames, backfills and what to do when a migration fails halfway.
Subagents and hooks
None of its own. The foundation agents and guard hooks still ship.
Sanity blog
2
Skills
2
Rules
5
Solution docs
Rules (2)
Read tokens stay on the server, Portable Text uses design tokens
src/app/(sanity)/** · src/app/studio/** · src/app/api/draft-mode/** · src/app/api/sanity/** · src/components/sanity/** · sanity/lib/**
The schema is code, and every GROQ query lives in one file
sanity/** · sanity.config.ts
Skills (2)
/add-sanity-type
Add or extend a Sanity document type end to end (schema, GROQ projection, result type, renderer and cache tags) so nothing renders blank.
/preview-draft
Set up or debug Sanity draft previews: the enable route, the secret, the banner, and why an editor is still seeing published content.
Subagents and hooks
None of its own. The foundation agents and guard hooks still ship.
What each one already knows
Solution docs land in docs/solutions/ in your repo and are published here, so you can read the failure modes before you commit.
Payload blog (5)
- Payload access control when your app already has authTwo user tables is the right answer. Map your app's roles onto Payload's rules instead of merging the tables, and never leave an access block undeclared.docs/solutions/blog-payload/access-control-matching-your-auth-battery.md
- Payload uploads vanish after a deploy: move media to an object storestaticDir writes to a filesystem that disappears on serverless. Add a storage adapter, keep the database rows, and migrate the files you already have.docs/solutions/blog-payload/media-on-an-object-store.md
- Payload migrations on Vercel without a broken deployVercel runs your build, not your migrations. Run them in the build command, forward-only, and split the schema deploy from the code that needs it.docs/solutions/blog-payload/migrations-on-vercel.md
- Running Payload inside the same Next.js appThe admin panel is a route group, not a second service. Route groups, withPayload, the import map and why your blog pages must not fetch their own API.docs/solutions/blog-payload/payload-inside-the-same-next-app.md
- Letting Payload share your Postgres without wrecking your migrationsTwo migration tools in one database will fight over table names and drop each other's tables. A separate schema keeps them apart for one config line.docs/solutions/blog-payload/sharing-postgres-with-your-app-tables.md
Sanity blog (5)
- Draft mode is on, and the page still shows published contentDraft mode only bypasses caches for reads that know about it. One direct client.fetch, or a token-less client, and the editor sees yesterday's copy.docs/solutions/blog-sanity/draft-mode-and-the-app-router-cache.md
- GROQ projections that stop your blog index fetching every article body*[_type == "post"] returns whole documents, drafts of fields included. Project explicitly, dereference in the query, and slice every list.docs/solutions/blog-sanity/groq-projections-that-avoid-over-fetching.md
- Sanity images without the 4MB original and the layout jumpasset->url hands the browser the raw upload. Build a transform URL with a width, and use the LQIP Sanity already generated as the blur placeholder.docs/solutions/blog-sanity/image-urls-and-lqip.md
- Modelling references in Sanity without an N+1 on every pageA reference is a pointer, not an embed. Dereference inside the projection, model the direction that reads well, and never resolve in a loop.docs/solutions/blog-sanity/modelling-references-without-n-plus-one.md
- Webhook revalidation or a revalidate timer: pick oneA 60-second timer rebuilds pages nobody asked for and still makes editors wait. Tag every read, invalidate on publish, and stop guessing.docs/solutions/blog-sanity/webhook-revalidation-vs-time-based.md
Which one to pick
From meta.bestFor and meta.tradeoffs. If a claim is not in the manifest, it is not on this page.
Pick Payload blog when
Teams who want editors in an admin UI but will not put their content in someone else's database. Strong when CMS content joins application data. Also when you need custom fields, hooks or access rules a hosted CMS will not give you. Or when data residency and export matter.
And accept that(5)
- You operate it. Migrations, backups, upgrades and the admin bundle's build time are yours. A hosted CMS has none of that, and that is the whole trade.
- It shares your database connection budget. The admin panel and your app draw from the same pool, so on serverless a pooled connection string is not optional.
- Cold starts are real. The admin routes load Payload, the database driver and the editor bundle, so the first hit after idle is slow. It is an internal tool, so this is usually fine. Tell your editors before they report it as a bug.
- Uploads need an object store before you deploy. Local disk works on your laptop and disappears on serverless. The media collection ships pointed at
public/mediaand must be repointed. - Schema changes are code plus a migration. That is a feature in review and a speed bump when an editor asks for one more field.
Pick Sanity blog when
Teams where the people writing are not the people deploying: marketing, content and design working next to engineering. Best when content is structured (authors, categories, references between documents), not a pile of long markdown files. Also when you need previews, roles and a real-time editor without building one.
And accept that(6)
- Content lives in someone else's database. Losing access to the project, or a pricing change, is a business risk a folder of markdown does not have. Export regularly. The dataset export is one CLI command.
- GROQ is a new query language for the team. Projections make over-fetching easy to avoid, but nobody arrives knowing it, and a bad projection fails silently.
- Caching becomes your job. Content changes without a deploy, so you need an answer to "why is this page stale?". The tags and webhook route in this battery are that answer.
- The Studio is a dependency of your app. Mounting it at /studio adds Sanity and styled-components to your install. The route is static and code-split, but the install is bigger.
- Sanity Studio v6 needs Node.js 22.12 or later, and next-sanity 13 needs Next.js 16. The generated package.json sets
engines.nodeto>=22.12, which Vercel reads. On Node 20 the app still builds, but thesanityCLI refuses to run. Set Node 22 or 24 on any other host or build image. - Structured content costs more upfront and pays off later. Modelling authors and categories as references takes an afternoon and saves you the day you want an author page.
Questions people actually ask
- Should I choose Payload blog or Sanity blog?
- Payload blog is best for teams who want editors in an admin UI but will not put their content in someone else's database. Strong when CMS content joins application data. Also when you need custom fields, hooks or access rules a hosted CMS will not give you. Or when data residency and export matter. Sanity blog is best for teams where the people writing are not the people deploying: marketing, content and design working next to engineering. Best when content is structured (authors, categories, references between documents), not a pile of long markdown files. Also when you need previews, roles and a real-time editor without building one. Both fill the blog slot, so a generated repo carries one or the other, never both.
- How much do Payload blog and Sanity blog cost?
- Payload blog: Free and open source (MIT). You host it, so the cost is the Postgres and Vercel functions you already pay for. Payload also sells an Enterprise plan with dedicated support and SSO. It has no public price. Sanity blog: Free plan: up to 20 seats, 2 public datasets and 10k documents. Growth is $15 per seat per month: up to 50 seats, private datasets and 25k documents. Past its included quotas, Growth bills pay-as-you-go. Enterprise is custom.
- What changes in my repo if I switch from Payload blog to Sanity blog?
- Payload blog writes 17 files, 1 environment variable and 5 dependencies, and installs 2 path-scoped rules, 2 skills and 5 solution docs. Sanity blog writes 21 files, 4 environment variables and 7 dependencies, and installs 2 path-scoped rules, 2 skills and 5 solution docs.
Decide once, then build the repo that already knows the decision.
Either way you get that choice’s rules, skills and solution docs installed, plus the guard hooks, an onboarding doc for exactly these env vars, and the Compound Engineering loop. Free and MIT.