Skip to content

Ghost Headless CMS — Ghost as Headless CMS with React, Next.js and Vue

DodaTech Updated 2026-06-28 9 min read

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:

  1. Content API — Read published content (public, no auth needed)
  2. Admin API — Full CRUD operations (authenticated)
  3. Webhooks — Real-time notifications for content changes
  4. Member authentication — Magic link auth for members
  5. Image transformation — Built-in image processing
  6. 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:

  1. Include the Portal script in your frontend.
  2. 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:

  1. Build a signup form that calls Ghost's magic link endpoint.
  2. Handle the magic link redirect in your frontend.
  3. 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

  1. 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).

  2. 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.

  3. Ignoring image optimization: Ghost's image processing (img_url helper, size variants) is a theme feature. In headless mode, you need to handle responsive images yourself or use a separate image CDN.

  4. 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.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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

Do I need Ghost(Pro) for headless mode?

No. Headless Ghost works with both Ghost(Pro) and self-hosted Ghost. The Content API is available on all Ghost installations. You just need a Content API key.

Can I use Ghost's image processing in headless mode?

Ghost's server-side image processing works on the images you upload. You can construct URLs manually: https://yoursite.com/content/images/size/w600/image.jpg. For full control, use an external image CDN like Cloudinary or Imgix.

How does member authentication work with a custom frontend?

Members authenticate via Ghost's magic link system. Your frontend redirects to Ghost for authentication, or you integrate the Ghost Portal SDK. For custom flows, use the Admin API to manage members from your backend.

Can I use Ghost as a headless CMS for a mobile app?

Yes. The Content API is REST-based and returns JSON, which any mobile app can consume. Use the Content API for reading posts and the Admin API for content creation workflows.

What frontend frameworks work best with Ghost?

Next.js is the most popular choice due to its SSG/ISR capabilities and excellent SEO. Nuxt (Vue), Gatsby (React), SvelteKit, and Eleventy are also common. Any framework that can make HTTP requests works.

Mini Project

Your task: Build a complete headless Ghost site with Next.js.

  1. Set up a Ghost installation with test content.
  2. Create a Next.js project with the Ghost Content API client.
  3. Build pages: homepage (post list with excerpts), individual post pages (full content), tag pages (filtered by tag).
  4. Implement Incremental Static Regeneration with revalidate.
  5. Add SEO metadata for each page (meta title, description, Open Graph, JSON-LD).
  6. Set up a revalidation webhook from Ghost to Next.js.
  7. 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:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro