blog · side by side
MDX blog vs Payload 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 MDX blog if
Engineering blogs, changelogs, docs-adjacent writing and any site where the writers already have commit access. Best when posts should go through review with the code they describe. Also when a post needs to import a real component from the app.
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.
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 AMDX blog | Option BPayload blog |
|---|---|---|
| In one line | MDX blog Posts are files in your repo. Reviewed in a pull request, shipped with the code. | Payload blog A full CMS in your own repo and database. No second service, no content API bill. |
| Pricing | MDX blog Free. No vendor, no seats, no API quota. The only cost is build time. | 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. |
| Best for | MDX blog Engineering blogs, changelogs, docs-adjacent writing and any site where the writers already have commit access. Best when posts should go through review with the code they describe. Also when a post needs to import a real component from the app. | 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. |
| Trade-offsVerbatim from the manifest | MDX blog
| Payload blog
|
| Required companionsAdded for you, with a reason | MDX blog Nothing. It stands on its own. | Payload blog
|
| Recommended alongsideSuggested, never added for you | MDX blog Nothing suggested. | Payload blog
|
| Env vars you will manageEvery one documented in docs/onboard.md | MDX blog 0 variables None. | Payload blog 1 variable · 1 required
|
| Dependencies added | MDX blog
| Payload blog
|
| MCP serversWritten into .mcp.json | MDX blog None. No extra agent tools from this one. | Payload blog None. No extra agent tools from this one. |
| Footprint in your repo | MDX blog 13 files, plus 3 injections into shared stack files | Payload blog 17 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 MDX blog (13)
content/2 files
blog/2 files
- hello-world.mdx
- writing-with-mdx.mdx
scripts/1 file
blog/1 file
- check-posts.ts
src/10 files
app/5 files
blog/4 files
[slug]/2 files
- opengraph-image.tsx
- page.tsx
rss.xml/1 file
- route.ts
- page.tsx
- sitemap.ts
components/2 files
blog/2 files
- callout.tsx
- post-card.tsx
lib/2 files
blog/2 files
- frontmatter.ts
- posts.ts
- mdx-components.tsx
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
Shared stack files MDX blog injects into
- nav-links
- next-config-wrappers
- verify-checks
Shared stack files Payload blog injects into
- bare-route-groups
- env-required
- nav-links
- next-config-wrappers
- verify-checks
MDX blog in your .env.local
# MDX blog adds no environment variables.
Payload blog in your .env.local
# required
PAYLOAD_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 content/**.
MDX blog
2
Skills
2
Rules
5
Solution docs
Rules (2)
Posts are validated content, not free-form files
content/**
The blog compiles at build time and styles itself with tokens
src/app/blog/** · src/app/sitemap.ts · src/lib/blog/** · src/components/blog/** · src/mdx-components.tsx
Skills (2)
/add-mdx-component
Add a component that posts can use without importing it, registered in src/mdx-components.tsx and styled with design tokens only.
/new-post
Draft, validate and publish a new MDX post in content/blog, with frontmatter that passes the checker and a slug that will never change.
Subagents and hooks
None of its own. The foundation agents and guard hooks still ship.
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.
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.
MDX blog (5)
- MDX in the App Router without shipping a compilernext-mdx-remote compiles every post on every request. Compile at build time with @next/mdx instead, and keep frontmatter with gray-matter.docs/solutions/blog-mdx/mdx-without-a-runtime.md
- OG images that render your font, not NotoImageResponse has no system fonts and silently falls back. Load a static TTF from disk, and know why woff2 and variable fonts fail.docs/solutions/blog-mdx/og-images-that-render-your-font.md
- An RSS feed that actually validatesUnescaped ampersands, ISO dates and a missing atom:link are why feed readers reject your feed. Generate it as a static route with escaped text.docs/solutions/blog-mdx/rss-that-validates.md
- A sitemap and canonical URLs that do not fight each otherlastModified from new Date() teaches crawlers to ignore your dates, and a canonical built from request headers points at your preview deploys.docs/solutions/blog-mdx/sitemap-and-canonical-urls.md
- Syntax highlighting without a 300kb bundlePrism and highlight.js in a client component ship a parser to every reader. Highlight at build time with a rehype plugin and ship CSS instead.docs/solutions/blog-mdx/syntax-highlighting-without-a-huge-bundle.md
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
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 MDX blog when
Engineering blogs, changelogs, docs-adjacent writing and any site where the writers already have commit access. Best when posts should go through review with the code they describe. Also when a post needs to import a real component from the app.
And accept that(5)
- Publishing needs a deploy. A typo fix is a commit, a build and a rollout. Fine for engineers. Painful for a marketing team that expects a "publish" button.
- No editor. Writers work in a code editor with YAML frontmatter and Git. The only preview link is a deploy preview, and the only permissions are repository access.
- No image pipeline. Images live in
public/, and you size and compress them. A hosted CMS gives you transforms and a CDN. - Content and code share a release cycle. Reverting a bad deploy reverts the posts published in it, and a content-only change still runs your whole build.
- Everything is yours. No API keys to rotate, no dataset to lose access to, no pricing change to absorb. The whole archive greps in one command.
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.
Questions people actually ask
- Should I choose MDX blog or Payload blog?
- MDX blog is best for engineering blogs, changelogs, docs-adjacent writing and any site where the writers already have commit access. Best when posts should go through review with the code they describe. Also when a post needs to import a real component from the app. 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. Both fill the blog slot, so a generated repo carries one or the other, never both.
- How much do MDX blog and Payload blog cost?
- MDX blog: Free. No vendor, no seats, no API quota. The only cost is build time. 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.
- What changes in my repo if I switch from MDX blog to Payload blog?
- MDX blog writes 13 files, 0 environment variables and 8 dependencies, and installs 2 path-scoped rules, 2 skills and 5 solution docs. Payload blog writes 17 files, 1 environment variable and 5 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.