blog · side by side
MDX 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 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 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 AMDX blog | Option BSanity blog |
|---|---|---|
| In one line | MDX blog Posts are files in your repo. Reviewed in a pull request, shipped with the code. | Sanity blog A real editorial CMS your writers can use, with the schema still living in your repo. |
| Pricing | MDX blog Free. No vendor, no seats, no API quota. The only cost is build time. | 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 | 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. | 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 | MDX blog
| Sanity blog
|
| Required companionsAdded for you, with a reason | MDX blog Nothing. It stands on its own. | Sanity blog Nothing. It stands on its own. |
| Recommended alongsideSuggested, never added for you | MDX blog Nothing suggested. | Sanity blog Nothing suggested. |
| Env vars you will manageEvery one documented in docs/onboard.md | MDX blog 0 variables None. | Sanity blog 4 variables · 4 required
|
| Dependencies added | MDX blog
| Sanity blog
|
| MCP serversWritten into .mcp.json | MDX blog None. No extra agent tools from this one. | Sanity blog None. No extra agent tools from this one. |
| Footprint in your repo | MDX blog 13 files, plus 3 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 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 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 MDX blog injects into
- 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
MDX blog in your .env.local
# MDX blog adds no environment variables.
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 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.
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.
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
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 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 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 MDX blog or Sanity 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. 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 MDX blog and Sanity blog cost?
- MDX blog: Free. No vendor, no seats, no API quota. The only cost is build time. 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 MDX blog to Sanity blog?
- MDX blog writes 13 files, 0 environment variables and 8 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.