Skip to main content
ASoc
Comparison

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.

The ASoc Team8 min read

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

AxisGhostNext.js
What it isA publishing platform (Node.js) with an admin editorA React framework; you build the site
Writing experienceBuilt-in editor, scheduling, drafts, no codeMDX files in git, or a CMS you add
Memberships, paid tiers, newslettersBuilt inYou build them (auth, payments, email)
ThemingHandlebars themesReact components, any design system
Pages that are not postsPages and static routes, within the theme modelAnything you can write a function for
HostingGhost's hosted service, or self-hosted Node plus a databaseStatic export or a Node/serverless host
Content in version controlNo — it lives in Ghost's databaseYes, if you use MDX
Publishing latencySecondsA deploy, or revalidation with ISR
Type safety on post metadataNone by defaultCompile-time, if you write the types
Review workflowWhatever the editor offersPull 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.

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 situationPick
The publication is the product; paid newsletterGhost
Non-technical writers publish dailyGhost, or headless Ghost
Blog is one section of a site with a catalog and accountsNext.js
Posts must reference your own database (products, plans)Next.js
You want content reviewed in pull requestsNext.js with MDX
Writers want an editor, the site needs custom pagesHeadless Ghost + Next.js

Troubleshooting the decision

SymptomLikely causeFix
"Editing a post takes a deploy"Content lives in gitAccept it, or move prose to a headless CMS; see ISR for revalidation without a rebuild
Related posts all point at the newest articlesRail uses recency, so links concentrateUse a wrapping window over a stable sort, as above
A post links to a product that no longer existsFree-text URLs in contentReference catalog slugs and test that they resolve
RSS feed fails validationUnescaped & or < in titlesEscape the five XML entities in the route handler
Pages for Ghost-hosted posts don't match the main siteTwo themes, two design systemsGo 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.

Keep reading

Comparison9 min read

Next.js vs. Nuxt: 24 of 92 Components Opt Into the Client

Not React versus Vue — the real split is where each framework lets you draw the server/client boundary. Next.js draws it through the import graph; Nuxt's default is universal.

Read more
Comparison11 min read

Next.js vs React + Vite for Admin Dashboards: How to Choose

Both ship excellent dashboards. The decision comes down to where your data lives, whether you need SEO, and who deploys it — not to raw performance.

Read more
Comparison9 min read

Next.js vs SvelteKit: Where a Write Is Allowed to Live

Bundle size is the wrong axis for a meta-framework choice. The real divide is routing defaults and whether a mutation can live anywhere or only on the route that owns it.

Read more