Next.js vs Ghost: Pick by Who Publishes, Not by Framework
Ghost is a publishing platform; Next.js is a framework. A comparison from a 361-post MDX blog beside a 111-product storefront, plus the link-graph defect only code could fix.
Ghost is a publishing platform: an editor, memberships, paid newsletters and a theme engine in one install. Next.js is a React framework with none of that built in. For a blog that is the product, Ghost is faster to run. For a site where the blog is one surface among catalog pages, accounts and downloads, Next.js wins, and the two combine as headless Ghost plus a Next.js front end.
That framing matters because the two are rarely rivals for the same job. This post comes from the Next.js side of the line. The blog you are reading is 361 MDX files, about 640,000 words, in the same repository as a storefront with 111 products, no CMS involved. Below is what that setup does well, what it costs, and the specific places where Ghost would have been the better call.
The comparison that matters
| Axis | Ghost | Next.js |
|---|---|---|
| What it is | A publishing platform (Node.js) with an admin editor | A React framework; you build the site |
| Writing experience | Built-in editor, scheduling, drafts, no code | MDX files in git, or a CMS you add |
| Memberships, paid tiers, newsletters | Built in | You build them (auth, payments, email) |
| Theming | Handlebars themes | React components, any design system |
| Pages that are not posts | Pages and static routes, within the theme model | Anything you can write a function for |
| Hosting | Ghost's hosted service, or self-hosted Node plus a database | Static export or a Node/serverless host |
| Content in version control | No — it lives in Ghost's database | Yes, if you use MDX |
| Publishing latency | Seconds | A deploy, or revalidation with ISR |
| Type safety on post metadata | None by default | Compile-time, if you write the types |
| Review workflow | Whatever the editor offers | Pull requests |
Check Ghost's current documentation for hosting and pricing details before deciding; they change and are not something this post should freeze.
Where Ghost genuinely wins
If the product is the publication, Ghost removes work that Next.js hands you. Paid memberships, a newsletter with sending, a comment-free reading experience and an editor a non-developer can use on day one are all included. Rebuilding that on Next.js means authentication, a payments webhook, an email provider and a paywall rule, and you will own each of them forever.
The honest test: if the people writing are not the people who can open a pull request, a git-based blog is the wrong tool. This repository's blog works because everyone publishing is also reading diffs.
Where Next.js wins: the blog is one surface of many
1. The blog shares types with the rest of the site
Every post's metadata is one typed entry. The index grid, the home-page teaser, the sitemap and the RSS feed all read that registry without compiling any article:
// src/lib/blog.ts — an explicit slug → loader map, one line per post
const postLoaders: Record<string, () => Promise<{ default: ComponentType }>> = {
"nextjs-vs-framer": () => import("@/content/blog/nextjs-vs-framer.mdx"),
"supabase-auth-ui-react": () =>
import("@/content/blog/supabase-auth-ui-react.mdx"),
// ...one entry per published post
};
A post's relatedTemplates field is a list of catalog slugs, and a test fails if any of them stops existing in src/data/catalog.ts. A post can therefore never link to a retired product. In Ghost the equivalent is a hand-pasted URL that rots silently. That cross-reference between content and a product database is the capability Ghost's model does not have, and it is the one that matters for a marketplace.
2. Invariants you can assert
src/data/__tests__/blog.test.ts holds the rules that keep 361 posts consistent: every post has a registered loader, no loader lacks a post, dates are real calendar dates, descriptions fit a meta description, every relatedSpokes entry exists. A forgotten file is a failing npm test, not a broken page in production.
3. A defect that only a code-owned link graph could fix
In September 2026 Search Console reported 35 blog posts as never crawled. The cause was the "Keep reading" rail, which returned the newest posts in a cluster. In a 178-post archive that handed 12 posts every rail link on the site, three of them 111 each, and left 166 with none.
The fix was to replace "newest in cluster" with a wrapping sliding window over the cluster in slug order:
// src/lib/blog.ts
function ring(posts: BlogPostMeta[], slug: string, limit: number) {
const pool = [...posts].sort((a, b) => a.slug.localeCompare(b.slug));
const start = pool.findIndex((p) => p.slug === slug);
const others = start === -1 ? pool.length : pool.length - 1;
if (others <= 0) return [];
return Array.from(
{ length: Math.min(limit, others) },
(_, i) => pool[(start + 1 + i) % pool.length],
).filter((p) => p.slug !== slug);
}
Every post now gets exactly three inbound links, and npm test fails if any post drops to zero or one collects more than six. Ghost's related-posts logic is a theme helper you can adjust, but asserting a whole-archive link-graph property in CI is not something its model gives you.
What the Next.js route costs
- No editor. A typo fix is a commit and a deploy. This is the price of the typed registry and it is real.
- No memberships for free. The commerce layer on this site (auth, a signed payment webhook, entitlement-gated downloads) is code you would not write on Ghost.
- Your own SEO plumbing. The RSS feed here is a hand-written route handler,
src/app/blog/feed.xml/route.ts, with its own XML escaping, because one unescaped&in a title makes the feed unparseable in every reader. Ghost ships a feed.
The third option: headless Ghost with a Next.js front end
Ghost exposes a Content API, so a Next.js site can render its posts while writers keep the editor. This splits the difference, and it adds a second system to host and secure. It also loses Ghost's member-only content gating unless you rebuild it, so it suits public content with a non-technical author team. For the same decision with other CMSs, see MDX in the repo vs a headless CMS and headless WordPress alternatives.
Which one to pick
| Your situation | Pick |
|---|---|
| The publication is the product; paid newsletter | Ghost |
| Non-technical writers publish daily | Ghost, or headless Ghost |
| Blog is one section of a site with a catalog and accounts | Next.js |
| Posts must reference your own database (products, plans) | Next.js |
| You want content reviewed in pull requests | Next.js with MDX |
| Writers want an editor, the site needs custom pages | Headless Ghost + Next.js |
Troubleshooting the decision
| Symptom | Likely cause | Fix |
|---|---|---|
| "Editing a post takes a deploy" | Content lives in git | Accept it, or move prose to a headless CMS; see ISR for revalidation without a rebuild |
| Related posts all point at the newest articles | Rail uses recency, so links concentrate | Use a wrapping window over a stable sort, as above |
| A post links to a product that no longer exists | Free-text URLs in content | Reference catalog slugs and test that they resolve |
| RSS feed fails validation | Unescaped & or < in titles | Escape the five XML entities in the route handler |
| Pages for Ghost-hosted posts don't match the main site | Two themes, two design systems | Go headless, or keep the blog on a subdomain deliberately |
FAQ
Is Next.js a replacement for Ghost? No. Next.js is a framework, so it renders a site; Ghost is a content system with an editor and memberships. They compete only at the layer where both can serve a blog, and Ghost is the less work if a blog is all you need.
Can I use Ghost as the CMS for a Next.js site? Yes. Ghost's Content API supplies posts and your Next.js app renders them. You keep the editor and gain full control of the front end, at the cost of two deployments.
Which is better for SEO? Neither is inherently. Both can serve fast, crawlable HTML with proper metadata. The difference is control: in Next.js the sitemap, canonical tags, JSON-LD and internal-link rules are code you can test, as in the link-graph fix above.
Do I need a database to blog on Next.js? No. This blog has none: posts are MDX compiled at build time and the metadata is a TypeScript array. A database enters only when you add accounts or purchases.
Templates in this post
If you choose the Next.js route, a template removes the boilerplate of layout, metadata, sitemap and dark mode. ASoc Vault (a fintech SaaS landing page), ASoc Vox (an AI voiceover site) and ASoc Weave (an AI website builder page) are starting points in code you own.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For adjacent decisions, see Next.js vs Framer and WordPress vs Next.js.
