Headless WordPress Alternative: What 352 Posts Run On Instead
Three families of alternative, and the measured cost of dropping the CMS entirely: 352 MDX posts into 852 static pages in ~12s, plus what you give up.
A headless WordPress alternative is any content source that feeds a decoupled front end without running WordPress behind it — a purpose-built headless CMS, a git-based CMS, or plain files compiled at build time. This storefront took the third option: 352 MDX posts, 623,092 words, compiled into 852 static pages in about 12 seconds with no CMS, no API call and no webhook.
Most articles on this query are published by headless-CMS vendors, so they converge on the same answer: replace WordPress with our CMS. That may well be right for you. This one is written from the other end — a Next.js site that removed the CMS layer entirely — so you can see what that actually costs, including the parts that are worse.
The three families of alternative
| Option | Content lives in | Editors get | You operate |
|---|---|---|---|
| Headless WordPress | MySQL, behind WP | Full WP admin, plugins, scheduling | WP host, PHP, plugin updates, WPGraphQL, a cache layer |
| Purpose-built headless CMS | Vendor's cloud | A modelled editor UI, roles, previews | API keys, webhooks, revalidation, rate limits, a bill |
| Files at build time | Your git repo | A text editor and a pull request | Nothing extra — it is your existing build |
The honest framing of the decision is not "which is more modern". It is who edits, and how often. A marketing team publishing daily, with non-technical editors and scheduled posts, is the case headless WordPress and hosted CMSes exist for. A small team whose writers already use git is paying real money and real latency for an editor UI nobody opens.
This site is the second case, so it chose files. Two of its sibling comparisons work the first case in detail: Sanity vs headless WordPress and Strapi vs headless WordPress.
What "files at build time" looks like in practice
Three files have to agree for every post, and npm test fails if they drift:
src/data/blog.ts— a typed metadata registry (BlogPostMeta: slug, title, description, date, cluster, reading time, related products, tags).src/content/blog/<slug>.mdx— the prose.src/lib/blog.ts— an explicitslug → () => import(...)loader map.
Metadata is deliberately separate from prose. The index grid, the home-page teaser, sitemap.ts and the RSS feed all need to list 352 posts; none of them should compile 352 articles to do it. Splitting the registry out is what keeps that cheap.
The loader map is written out one line per post, which looks like busywork until you read the reason in the file:
/**
* The loader map is written out slug by slug ON PURPOSE. A dynamic
* `import(`../content/blog/${slug}.mdx`)` would work, but it hands the
* bundler a wildcard: every `.mdx` under that directory gets pulled into the
* graph, a typo resolves to a runtime error instead of a build error, and
* nothing type-checks.
*/
const postLoaders: Record<string, () => Promise<{ default: ComponentType }>> = {
"shadcn-tailwind-v4": () => import("@/content/blog/shadcn-tailwind-v4.mdx"),
"tailwind-properties": () => import("@/content/blog/tailwind-properties.mdx"),
// … 350 more
};
That is the trade a CMS makes for you and a file-based setup makes explicit: a missing CMS entry is a 404 at request time, a missing file here is a failed next build.
The defect that cost the most time
MDX is wired up in next.config.ts, and this is the comment that sits above it:
/**
* Plugins are named as STRINGS, not imported: Turbopack runs the MDX pipeline
* in Rust and cannot receive JavaScript function references. Passing the
* imported plugin here builds but silently drops it.
*/
const withMDX = createMDX({
options: {
remarkPlugins: ["remark-gfm"],
rehypePlugins: ["rehype-slug"],
},
});
import remarkGfm from "remark-gfm" and passing the function is the form every tutorial shows, and it compiles without a warning. It just does nothing. The failure surfaces far from the cause: MDX defaults to CommonMark, which has no table syntax, so every | a | b | row renders as a paragraph of literal pipe characters. On a blog whose comparison posts are mostly tables, that is most of the content quietly turning into gibberish, with a green build.
This is the structural difference between the two models. A CMS failure is usually loud — a 500, an auth error, a rate limit. A build-time content pipeline fails silently, in the rendering, and your defence is tests plus actually looking at the page.
What you give up
Worth being specific, because the vendor posts skip this and it is the real decision:
- No WYSIWYG, and no non-technical editing. Publishing means a branch, an
.mdxfile and a pull request. For an editor who does not use git, this option is simply not available. - Publishing requires a deploy. There is no "publish now" button. A typo fix is a commit and a build, not a save.
- No scheduled publishing, drafts workflow, or editorial roles unless you build them. WordPress has had these for twenty years.
- No media library. Images are files in
public/, right-sized by a build script rather than an upload UI. - Content is coupled to the app repo. One codebase, one deploy, one git history for code and prose together.
What you get
All of it measured in this repo rather than estimated:
| Property | Measured here |
|---|---|
| Posts / corpus | 352 .mdx files, 4.5 MB, 623,092 words |
| Static pages generated | 852, in 11–13s across 3 workers |
| Compile step | 17–19s across cold runs (Next.js 16.2.9, Turbopack) |
Full npm run build | ~42s wall, cold |
| Runtime content requests | Zero — no CMS host appears in the connect-src of the CSP in next.config.ts |
| Article typography | One file, src/mdx-components.tsx, 159 lines, 15 element overrides |
The last two rows are the ones that compound. With no content API in the request path there is nothing to rate-limit, nothing to authenticate, no webhook to miss and no stale-cache state to reason about — the "did the revalidation fire" class of bug does not exist, because publishing is the build. And because every article routes through 15 element overrides in one file, article styling is a single place rather than a per-post concern.
Choosing between the alternatives
| If this is true | Choose |
|---|---|
| Non-technical editors publish often; scheduling and roles matter | Headless WordPress, or a hosted headless CMS |
| You are already on WordPress and the editing UX is the thing you like | Headless WordPress with WPGraphQL — keep the admin, change the front end |
| Content is modelled, reused across several channels, edited by a team | A purpose-built headless CMS |
| Writers are developers; posts are long-form and versioned | Files at build time (MDX) |
| You want structured content and git review | A git-based CMS over your repo |
"Headless WordPress is slow" is not a good reason to migrate, and it is the reason these posts usually give. Headless WordPress behind a CDN is fast. The reasons that survive contact are operational: how many moving parts you maintain, and whether the editing UI is earning its keep.
Troubleshooting a file-based move
| Symptom | Cause | Fix |
|---|---|---|
Tables render as literal | pipes | remark-gfm passed as an imported function under Turbopack | Name plugins as strings in next.config.ts |
| Heading anchors missing | No rehype-slug in the pipeline | Add "rehype-slug" — ids are what in-page links need |
| A renamed file 404s forever | Slug changed after publishing | Never rename a published slug; the URL is the contract |
| New post never appears in the index | Metadata registry not updated | Registry, file and loader map must all three agree — enforce it in tests |
| Build fails only in CI | A typo'd import path that your editor resolved loosely | Keep imports explicit; this is the failure mode you want |
Frequently asked questions
Is headless WordPress still a reasonable choice in 2026? Yes, when the WP admin is the feature you are keeping. It stops being reasonable when nobody uses the admin and you are maintaining PHP, a database and a GraphQL layer to serve text that never changes.
Can I keep WordPress for editors and go static for readers?
That is exactly what headless WordPress with a build-time fetch gives you: editors stay in wp-admin, the front end renders statically. You still operate WordPress; you no longer serve from it.
Does dropping the CMS hurt SEO? Nothing about SEO depends on a CMS. Metadata, canonicals, JSON-LD, sitemap dates and an RSS feed are all generated here from the typed registry — see MDX vs a headless CMS for a blog for how those surfaces are wired.
What about 352 posts in one repo — does the build get slow? Not meaningfully so far: 852 pages in 11–13s of static generation, about 42s for the whole cold build. The per-post cost is small because the metadata registry is separate from the prose, so listing pages never compile articles.
Templates in this post
Each of these ships as a Next.js + Tailwind project with its content in typed files rather than behind a CMS, which is the setup described above: ASoc Ledger, ASoc Lens and ASoc Magnet.
Browse the sets: Next.js landing page templates and Tailwind landing page templates. For the same decision framed against a conventional WordPress build, read WordPress vs Next.js for a marketing site next.
