Ghost Headless CMS — Ghost as Headless CMS with React, Next.js and Vue
In this tutorial, you'll learn how to use Ghost as a headless CMS — decoupling the content backend from the frontend presentation layer and building custom frontends with Next.js, React, Vue, or any framework that can consume a REST API.
What You'll Learn
- What headless CMS means and why it matters
- Ghost's headless architecture and available APIs
- Building a Next.js site with Ghost as the backend
- Static site generation with Ghost and Next.js
- Building a React single-page app with Ghost
- Using Ghost with Vue.js and Nuxt
- Handling member authentication in headless mode
- SEO considerations for headless Ghost sites
- Performance optimization for headless setups
Why It Matters
Traditional CMS platforms tightly couple the backend (content management) with the frontend (theme templates). Ghost does this well with Handlebars themes, but sometimes you need more — a custom React frontend, a mobile app, a static site that builds to a CDN, or a multi-channel publishing system where the same content appears on a website, mobile app, and smart display. Headless Ghost decouples content from presentation, letting you use any frontend technology while keeping Ghost as your content management hub.
Real-World Use
A company redesigns their marketing site. The marketing team wants to manage content in Ghost's editor (familiar, clean, easy). The development team wants to build the frontend in Next.js for optimal performance and SEO. They set up Ghost headless: the marketing team writes content in Ghost, and the Next.js site fetches content via the Content API at build time, generating static pages. The site loads instantly, ranks well in search, and the marketing team never touches code.
Learning Path
flowchart LR A["Custom Integrations"] --> B["Headless Ghost
You are here"]:::current B --> C["SEO Settings"] C --> D["Sitemaps & Robots"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
What is a Headless CMS?
A headless CMS provides content management capabilities (editing, storing, organizing) without imposing a frontend presentation layer.
flowchart LR A["Ghost Admin"] --> B["Content API"] B --> C["Next.js Site"] B --> D["Mobile App"] B --> E["Smart TV App"] B --> F["Email Template"] style B fill:#38bdf8,color:#0f172a
With traditional Ghost (themed), the flow is:
Ghost Admin → Ghost Server → Ghost Theme → Browser
With headless Ghost, the flow is:
Ghost Admin → Ghost Server → Content API → Your Frontend → Browser
Ghost's Headless Architecture
Ghost provides everything you need for a headless setup:
- Content API — Read published content (public, no auth needed)
- Admin API — Full CRUD operations (authenticated)
- Webhooks — Real-time notifications for content changes
- Member authentication — Magic link auth for members
- Image transformation — Built-in image processing
- Custom routes — URL structure control
What You Lose Going Headless
- Built-in theme rendering (you build the frontend yourself)
- Ghost's built-in Caching (you handle caching)
- Automatic SEO metadata injection (you implement it)
- Ghost's admin preview loses accuracy
- Member Portal integration requires custom work
Option 1: Next.js Static Site Generation
Next.js is the most popular choice for headless Ghost because it supports static site generation (SSG).
Project Setup
npx create-next-app@latest ghost-frontend
cd ghost-frontend
npm install @tryghost/content-api
Ghost Configuration
// lib/ghost.js
import GhostContentAPI from '@tryghost/content-api';
const api = new GhostContentAPI({
url: process.env.GHOST_API_URL,
key: process.env.GHOST_CONTENT_API_KEY,
version: 'v5.0'
});
export async function getPosts() {
return await api.posts.browse({
limit: 'all',
include: 'tags,authors'
});
}
export async function getPost(slug) {
return await api.posts.read({ slug }, { include: 'tags,authors' });
}
export async function getPages() {
return await api.pages.browse({ limit: 'all' });
}
Static Page Generation
// pages/index.js
import { getPosts } from '../lib/ghost';
export async function getStaticProps() {
const posts = await getPosts();
return {
props: { posts },
revalidate: 60 // Revalidate every 60 seconds (ISR)
};
}
export default function Home({ posts }) {
return (
<div>
<h1>My Ghost Blog</h1>
{posts.map(post => (
<article key={post.id}>
<h2><Link href={`/${post.slug}`}>{post.title}</Link></h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Dynamic Routes
// pages/[slug].js
import { getPosts, getPost } from '../../lib/ghost';
export async function getStaticPaths() {
const posts = await getPosts();
const paths = posts.map(post => ({ params: { slug: post.slug } }));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return { props: { post }, revalidate: 60 };
}
export default function PostPage({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
}
Incremental Static Regeneration (ISR)
Next.js ISR lets you update static content without rebuilding the entire site:
export async function getStaticProps() {
const posts = await getPosts();
return {
props: { posts },
revalidate: 60 // Regenerate at most once every 60 seconds
};
}
When a new post is published in Ghost, the next request within 60 seconds triggers a background regeneration of the page.
Webhooks for Instant Updates
For instant updates, set up a Ghost Webhook that calls a Next.js API route:
// pages/api/revalidate.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).send('Method not allowed');
}
try {
await res.revalidate('/');
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}
Configure a Ghost webhook with post.published pointing to https://yoursite.com/api/revalidate.
Option 2: React SPA with Ghost
For dynamic single-page applications, use the Ghost Content API directly from the browser.
// App.js
import { useEffect, useState } from 'react';
function GhostBlog() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(`https://yoursite.com/ghost/api/content/posts/?key=${API_KEY}&include=authors`)
.then(res => res.json())
.then(data => setPosts(data.posts));
}, []);
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</div>
))}
</div>
);
}
Option 3: Vue.js / Nuxt with Ghost
// nuxt.config.js
export default {
publicRuntimeConfig: {
ghost: {
url: process.env.GHOST_API_URL,
key: process.env.GHOST_CONTENT_API_KEY
}
}
};
// pages/index.vue
<script setup>
const config = useRuntimeConfig();
const { data: posts } = await useFetch(
`${config.public.ghost.url}/ghost/api/content/posts/`,
{
params: {
key: config.public.ghost.key,
include: 'authors',
limit: 10
}
}
);
</script>
<template>
<div>
<article v-for="post in posts.posts" :key="post.id">
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt }}</p>
</article>
</div>
</template>
Member Authentication in Headless Mode
Portal Integration
In headless mode, the Ghost Portal cannot be loaded automatically. You need to:
- Include the Portal script in your frontend.
- Initialize it with your Ghost site URL.
<script src="https://yoursite.com/public/ghost-sdk.min.js"></script>
<script>
// Initialize Ghost SDK
const ghost = new Ghost({ url: 'https://yoursite.com' });
// Get member info
ghost.init().then(() => {
if (ghost.member) {
console.log('Member:', ghost.member);
}
});
</script>
Custom Auth Flow
For full control, implement your own authentication flow:
- Build a signup form that calls Ghost's magic link endpoint.
- Handle the magic link redirect in your frontend.
- Validate member sessions using Ghost's member API.
SEO for Headless Ghost
Ghost handles SEO metadata in themed mode automatically. In headless mode, you need to implement it yourself.
// Next.js SEO component
function SEO({ post }) {
return (
<Head>
<title>{post.meta_title || post.title}</title>
<meta name="description" content={post.meta_description || post.excerpt} />
<link rel="canonical" href={post.canonical_url || `${siteUrl}/${post.slug}`} />
<meta property="og:title" content={post.og_title || post.title} />
<meta property="og:description" content={post.og_description || post.excerpt} />
<meta property="og:image" content={post.og_image || post.feature_image} />
<meta name="twitter:card" content="summary_large_image" />
<script type="application/ld+json">
{JSON.stringify(generateArticleSchema(post))}
</script>
</Head>
);
}
Common Mistakes
Not implementing caching for API calls: The Content API is fast, but every page load should not trigger an API request. Implement caching at the framework level (Next.js ISR, React Query, SWR, or localStorage).
Forgetting SEO in headless mode: Ghost's automatic SEO metadata is only available in themed mode. In headless mode, you must implement meta titles, descriptions, Open Graph, JSON-LD, and canonical URLs manually.
Ignoring image optimization: Ghost's image processing (
img_urlhelper, size variants) is a theme feature. In headless mode, you need to handle responsive images yourself or use a separate image CDN.Missing webhook-based rebuilds: If you use static generation and do not set up webhooks, your site only updates when you manually rebuild. Set up revalidation webhooks for automatic updates.
Not planning for fallback content: When Ghost is down or unreachable, your frontend should handle errors gracefully — show cached content or a friendly error message rather than breaking completely.
Practice Questions
What is the difference between themed Ghost and headless Ghost? Answer: Themed Ghost uses Handlebars templates on the Ghost server to render HTML. Headless Ghost exposes content via the Content API for a custom frontend to consume. In themed mode, Ghost handles routing, SEO, caching, and rendering. In headless mode, the frontend handles these.
How does Incremental Static Regeneration (ISR) work with Next.js and Ghost? Answer: ISR generates static pages at build time and regenerates them in the background when traffic arrives. With
revalidate: 60, the page is served from cache. After 60 seconds, the first request triggers a background rebuild. You can also use a Ghost webhook to trigger immediate revalidation.What SEO considerations are different in headless Ghost? Answer: You must implement meta tags, Open Graph, Twitter cards, JSON-LD structured data, and canonical URLs in your frontend code. Ghost does not inject these automatically in headless mode. You also need to generate a sitemap and robots.txt from your frontend framework.
Challenge: Build a headless Ghost site using Next.js. Set up Ghost as the content backend, create a Next.js project, fetch all posts using the Content API with ISR, implement proper SEO tags, set up a webhook from Ghost to trigger revalidation on new posts, and deploy the site.
FAQ
Mini Project
Your task: Build a complete headless Ghost site with Next.js.
- Set up a Ghost installation with test content.
- Create a Next.js project with the Ghost Content API client.
- Build pages: homepage (post list with excerpts), individual post pages (full content), tag pages (filtered by tag).
- Implement Incremental Static Regeneration with
revalidate. - Add SEO metadata for each page (meta title, description, Open Graph, JSON-LD).
- Set up a revalidation webhook from Ghost to Next.js.
- Deploy the site and verify it works end-to-end.
This exercise gives you a production-ready headless Ghost architecture.
What's Next
Now that you can use Ghost headless, it is time to optimize for search engines:
Continue to Lesson 30: SEO Settings — Meta titles, descriptions, canonical URLs, and structured data.
Related lessons:
- Sitemaps and Robots — Sitemaps and crawler control
- Structured Data — JSON-LD and schema markup
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro