Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Ghost with V0

To use Ghost as a headless CMS with V0 by Vercel, fetch posts and pages from Ghost's Content API in your Next.js Server Components or API routes. V0 generates the blog listing and post page UI; your Next.js app fetches data from Ghost using your Content API key. Deploy to Vercel with ISR for fast, always-fresh blog pages.

What you'll learn

  • How to connect Ghost's Content API to a Next.js App Router project
  • How to generate blog listing and post page layouts with V0
  • How to implement ISR with on-demand revalidation triggered by Ghost webhooks
  • How to handle Ghost's HTML post content safely in Next.js
  • How Ghost as a headless CMS differs from WordPress REST API for V0 projects
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read25 minutesCMSApril 2026RapidDev Engineering Team
TL;DR

To use Ghost as a headless CMS with V0 by Vercel, fetch posts and pages from Ghost's Content API in your Next.js Server Components or API routes. V0 generates the blog listing and post page UI; your Next.js app fetches data from Ghost using your Content API key. Deploy to Vercel with ISR for fast, always-fresh blog pages.

Ghost as a Modern Headless Blog Backend for V0 Apps

Ghost started as a WordPress alternative focused purely on blogging and newsletters. Over time it evolved into a powerful headless CMS with a clean, well-documented REST API — making it an excellent backend for Next.js frontends built with V0. The editorial experience is significantly better than WordPress for content creators: a distraction-free editor, built-in newsletter management, and membership/subscription tools. The technical experience for developers is equally clean: the Content API returns structured JSON with full post content, authors, tags, and metadata in a single request.

The integration architecture is simpler than most CMS setups because Ghost's Content API uses a public key — no OAuth flow, no server-side token exchange. You can fetch posts directly in React Server Components using the @tryghost/content-api SDK or raw fetch calls, and the data renders on the server before the page reaches the browser. This is the optimal pattern for SEO-focused blog pages: full HTML is available to crawlers, no loading states for content, and you can add ISR to automatically revalidate pages when new posts are published in Ghost.

For V0 users, the workflow is clean: generate the blog UI in V0 (post card grid, single post layout, author page), then wire the component's data props to Ghost API calls in Next.js page files. Your content team manages everything in Ghost's editor while the V0-generated frontend handles the presentation. This tutorial covers the complete setup from creating a Ghost Content API key to deploying a fast, SEO-ready blog on Vercel.

Integration method

Next.js API Route

V0 generates the React components for blog listing pages, individual post pages, and author profiles. Your Next.js App Router pages fetch Ghost content using the @tryghost/content-api SDK or direct REST API calls from Server Components. Because Ghost's Content API requires only a public API key, data fetching works directly in React Server Components without a proxy API route — though caching and ISR are handled at the Next.js level via Vercel.

Prerequisites

  • A V0 account at v0.dev with an active project
  • A Ghost instance — either Ghost(Pro) hosted plan at ghost.org or self-hosted Ghost on a VPS
  • A Ghost Content API key created in Ghost Admin → Integrations → Add custom integration
  • Your Ghost site's URL (e.g., https://yourblog.ghost.io or https://blog.yourdomain.com)
  • A Vercel account for deployment — ISR and on-demand revalidation require Vercel or a compatible host

Step-by-step guide

1

Create a Ghost Content API Key

Before writing any code, you need a Content API key from your Ghost instance. This is a public read-only key — unlike admin API keys, it only provides read access to published posts and pages, so it's safe to use in your Next.js Server Components without a server-side proxy. Log in to your Ghost Admin panel (usually at yourghost.io/ghost or yourdomain.com/ghost). Navigate to Settings → Integrations → Add custom integration. Give it a name like 'V0 Next.js Frontend' and click Create. Ghost generates a Content API key (a long alphanumeric string) and shows you the API URL. Copy both the Content API key and the API URL. The API URL is typically your Ghost site's root URL (e.g., https://yourblog.ghost.io) — Ghost appends /ghost/api/content/ to it automatically when you use the SDK. If you're self-hosting Ghost, make sure your Ghost instance is accessible from the internet (not just localhost) before proceeding, because Vercel's build process will fetch content from Ghost during the build phase. Also check that your Ghost version is 3.x or higher — the Content API was significantly improved in Ghost 3.0 and the @tryghost/content-api SDK targets version 3+.

Pro tip: Ghost(Pro) plans include a CDN for media files. If you're self-hosting, consider putting Cloudflare in front of Ghost to cache API responses and media — this reduces load on your Ghost server when Vercel fetches content during builds.

Expected result: You have a Ghost Content API key (format: a long hex string) and your Ghost site URL. Both are ready to be added to your Next.js environment variables.

2

Generate the Blog UI in V0

Open V0 and describe the blog layouts you need. For a typical Ghost-powered blog, you'll need at least two page layouts: a post listing page (home or /blog) and a single post page (/blog/[slug]). Prompt V0 for each layout separately to get focused, production-quality results. For the listing page, describe the post card component with the data fields Ghost returns: title, feature_image (cover photo URL), custom_excerpt or excerpt, primary_author (author object with name and profile_image), primary_tag, published_at (date string), and reading_time (number, minutes). For the single post page, describe the full article layout including where the post body HTML will be injected. Ask V0 to use a clean, readable typography system — Ghost posts often contain code blocks, pull quotes, and image galleries, so the stylesheet needs to handle varied content types. In V0's Design Mode, adjust the typography and spacing to match your brand. The post body HTML that Ghost returns includes its own class names (kg-card for Koenig editor blocks, kg-image, kg-gallery-card, etc.) — you'll want to add CSS for these in your global stylesheet. Ask V0 to generate placeholder content that mimics real blog posts, including fake images, author names, and multi-paragraph excerpts, so you can evaluate the layout before connecting real Ghost data.

V0 Prompt

Create a blog post listing page with a hero section showing the latest featured post (large background image, title overlay, author and date badges) and a 3-column responsive grid of post cards below. Each card has a cover image, tag badge, title, 2-line excerpt, author avatar and name, and publication date. Add pagination with Previous/Next buttons at the bottom. Use clean, modern typography with good reading contrast.

Paste this in V0 chat

Pro tip: Ask V0 to use realistic-length fake content in the post cards — short 3-word titles and single-line excerpts make layouts look deceptively good in preview but break at real content lengths.

Expected result: V0 generates a blog listing component and a post page layout component with proper data shape props. The layouts handle varied content lengths and include loading and empty states.

3

Install the Ghost Content API SDK and Fetch Posts

Install the @tryghost/content-api package, then create a Ghost API client module at lib/ghost.ts. This module initializes the Ghost Content API client with your site URL and Content API key, and exports helper functions for fetching posts, pages, tags, and authors. In your Next.js page files, import these helpers and call them in Server Components (no 'use client' directive) to fetch Ghost data at request time or build time. The @tryghost/content-api SDK returns typed response objects for posts, pages, tags, and authors. Each post object includes fields like id, title, slug, html (rendered post content), feature_image, custom_excerpt, excerpt, published_at, reading_time, primary_author, primary_tag, and tags. For the post listing page, call api.posts.browse({ limit: 12, include: ['authors', 'tags'] }) to get the first 12 posts with full author and tag data. For a single post page, call api.posts.read({ slug: params.slug }, { include: ['authors', 'tags'] }) to get a specific post by slug. Wrap the API calls in a try/catch and return null on error — Next.js not-found.tsx handles the 404 case gracefully. One important note about TypeScript: the @tryghost/content-api package has TypeScript type definitions but they're not always perfectly accurate for all Ghost versions. If you encounter type errors, you can create a local types file or use type assertions.

V0 Prompt

Create a lib/ghost.ts file that initializes the @tryghost/content-api client with process.env.GHOST_URL and process.env.GHOST_CONTENT_API_KEY. Export async functions: getPosts(page, limit) that fetches posts with author and tag includes, getPost(slug) that fetches a single post, and getTags() that fetches all tags with post counts.

Paste this in V0 chat

lib/ghost.ts
1import GhostContentAPI from '@tryghost/content-api';
2
3const api = new GhostContentAPI({
4 url: process.env.GHOST_URL!,
5 key: process.env.GHOST_CONTENT_API_KEY!,
6 version: 'v5.0',
7});
8
9export interface GhostPost {
10 id: string;
11 title: string;
12 slug: string;
13 html: string;
14 feature_image: string | null;
15 custom_excerpt: string | null;
16 excerpt: string;
17 published_at: string;
18 reading_time: number;
19 primary_author: { name: string; profile_image: string | null; slug: string };
20 primary_tag: { name: string; slug: string } | null;
21}
22
23export async function getPosts(page = 1, limit = 12): Promise<GhostPost[]> {
24 try {
25 return await api.posts.browse({
26 limit,
27 page,
28 include: ['authors', 'tags'],
29 order: 'published_at DESC',
30 }) as unknown as GhostPost[];
31 } catch (error) {
32 console.error('Ghost API error:', error);
33 return [];
34 }
35}
36
37export async function getPost(slug: string): Promise<GhostPost | null> {
38 try {
39 return await api.posts.read(
40 { slug },
41 { include: ['authors', 'tags'] }
42 ) as unknown as GhostPost;
43 } catch (error) {
44 return null;
45 }
46}
47
48export async function getTags() {
49 try {
50 return await api.tags.browse({ limit: 'all', include: ['count.posts'] });
51 } catch {
52 return [];
53 }
54}

Pro tip: Ghost's Content API supports powerful filter parameters — for example, filter: 'tag:tutorial+featured:true' to get only featured tutorial posts. Check the Ghost Content API docs for the full filter syntax.

Expected result: lib/ghost.ts exports working API helper functions. Calling getPosts() returns a typed array of Ghost post objects. The Next.js pages can import and call these functions in Server Components.

4

Build the Next.js Blog Pages with ISR

Create the actual Next.js page files that wire V0's UI components to Ghost API data. For the blog listing page at app/blog/page.tsx, create a Server Component that calls getPosts() and passes the data to your V0-generated listing component. For the individual post page at app/blog/[slug]/page.tsx, call getPost(params.slug) and pass the post data to your V0-generated post layout. The most important addition here is Incremental Static Regeneration (ISR) — configure your page with revalidate to automatically regenerate cached pages after a set interval. A revalidate of 3600 (1 hour) means Ghost post updates will appear on your site within an hour of publishing. For immediate updates, add a Ghost webhook that triggers on-demand revalidation via a Next.js revalidatePath API route. To render Ghost's HTML post content, use dangerouslySetInnerHTML with DOMPurify for sanitization. Ghost's HTML output is from a trusted source (your own CMS), but sanitizing prevents XSS if someone compromised your Ghost admin. Add a global stylesheet for Ghost's Koenig editor CSS classes — the kg-card, kg-image, kg-gallery-card, and kg-bookmark-card classes need styling to look correct. For generateStaticParams, return all post slugs at build time to pre-render every post as a static page.

V0 Prompt

Create app/blog/[slug]/page.tsx as a Next.js Server Component that calls getPost(params.slug) from lib/ghost.ts. Pass the post data to the PostPage component. Add export const revalidate = 3600 for ISR. Generate metadata using generateMetadata that returns the post title and excerpt as the page description. If getPost returns null, call notFound().

Paste this in V0 chat

app/blog/[slug]/page.tsx
1import { notFound } from 'next/navigation';
2import { getPost, getPosts } from '@/lib/ghost';
3
4export const revalidate = 3600; // Revalidate every hour
5
6interface Props {
7 params: { slug: string };
8}
9
10export async function generateMetadata({ params }: Props) {
11 const post = await getPost(params.slug);
12 if (!post) return {};
13 return {
14 title: post.title,
15 description: post.custom_excerpt || post.excerpt,
16 openGraph: {
17 images: post.feature_image ? [post.feature_image] : [],
18 },
19 };
20}
21
22export async function generateStaticParams() {
23 const posts = await getPosts(1, 'all' as unknown as number);
24 return posts.map((post) => ({ slug: post.slug }));
25}
26
27export default async function PostPage({ params }: Props) {
28 const post = await getPost(params.slug);
29 if (!post) notFound();
30
31 return (
32 <article className="max-w-2xl mx-auto px-4 py-12">
33 <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
34 <div className="flex items-center gap-3 text-sm text-gray-500 mb-8">
35 <span>{post.primary_author.name}</span>
36 <span>·</span>
37 <span>{new Date(post.published_at).toLocaleDateString()}</span>
38 <span>·</span>
39 <span>{post.reading_time} min read</span>
40 </div>
41 {post.feature_image && (
42 <img src={post.feature_image} alt={post.title} className="w-full rounded-lg mb-8" />
43 )}
44 <div
45 className="prose prose-lg max-w-none"
46 dangerouslySetInnerHTML={{ __html: post.html }}
47 />
48 </article>
49 );
50}

Pro tip: Add the Tailwind @tailwindcss/typography plugin and use the prose class on the Ghost HTML container — it automatically styles headings, lists, code blocks, and blockquotes to look great without custom CSS.

Expected result: Visiting /blog shows a server-rendered post listing fetched from Ghost. Visiting /blog/some-post-slug renders the full post with Ghost's HTML content. Pages are cached by ISR and automatically revalidate after one hour.

5

Configure Environment Variables and Deploy

Ghost requires two environment variables: GHOST_URL (your Ghost site's root URL, e.g., https://yourblog.ghost.io — no trailing slash) and GHOST_CONTENT_API_KEY (the public Content API key from Ghost Admin → Integrations). Both are safe to use in server-side code. The GHOST_CONTENT_API_KEY is technically public (it only allows reading published content) but convention is to keep it as a server-only variable without NEXT_PUBLIC_ prefix to avoid unnecessarily exposing it. Add both variables in Vercel Dashboard → Settings → Environment Variables, selecting the Production and Preview environments. After adding variables, push your code to GitHub and Vercel will auto-deploy. For immediate Ghost-to-Vercel updates (instead of waiting for ISR), set up a Ghost webhook: go to Ghost Admin → Integrations → your integration → Add webhook. Set the event to 'Post published' and the URL to https://your-vercel-app.vercel.app/api/revalidate?secret=YOUR_SECRET&path=/blog. Create the /api/revalidate route in Next.js using revalidatePath from next/cache. For complex Ghost setups with custom membership integrations, multiple publications, or Ghost's newsletter system, RapidDev can help structure the Next.js routing and ISR configuration for optimal performance.

.env.local
1# .env.local
2# Ghost CMS credentials
3GHOST_URL=https://yourblog.ghost.io
4GHOST_CONTENT_API_KEY=your_content_api_key_here
5
6# Optional: secret for on-demand ISR revalidation webhook
7REVALIDATE_SECRET=your_random_secret_here

Pro tip: Ghost's Content API returns a meta object with pagination data (total posts, pages, current page). Use meta.pagination.next to determine if there are more pages to load in your blog listing.

Expected result: All environment variables are set in Vercel. The deployed blog fetches and renders Ghost posts. New posts published in Ghost appear on the site within the ISR revalidation window, or immediately if you've set up the webhook.

Common use cases

Headless Blog with Ghost Editor + V0 Frontend

Run Ghost for its superior editorial experience while using a V0-generated Next.js frontend for the public-facing blog. Content creators write and publish in Ghost's clean editor; the Next.js ISR pages serve the content at CDN speed. This combination is popular for startup blogs, SaaS marketing sites, and personal publications.

V0 Prompt

Create a blog homepage with a featured post hero section at the top (large image, title, excerpt, author) and a 3-column grid of recent post cards below it. Each card shows the post's feature image, title, excerpt (truncated at 120 characters), author name, and publication date. Include a 'Load More' button at the bottom that fetches the next page of posts.

Copy this prompt to try it in V0

Individual Post Page with Ghost's HTML Content

Render individual blog posts using Ghost's pre-rendered HTML content. Ghost formats post content as clean HTML that you can safely inject into your Next.js page using dangerouslySetInnerHTML with sanitization. Add reading time, table of contents, author bio, and related posts to create a full editorial page layout.

V0 Prompt

Build a blog post page layout with a centered content column (max-width 720px). Include the post title in a large h1, author name and avatar on the left, publication date on the right, and an estimated reading time badge. Below the hero, add a content area where the post HTML will be rendered. At the bottom, show an author bio card and a 'More from this author' section with 3 post cards.

Copy this prompt to try it in V0

Tag and Author Archive Pages

Build archive pages that list all posts for a specific tag or author. Ghost's Content API supports filtering posts by tag slug or author slug, making it straightforward to create category and author landing pages. These pages are valuable for SEO and help readers navigate a large post library.

V0 Prompt

Create a tag archive page that shows the tag name as a large heading, a short tag description below it, and then a grid of post cards filtered by that tag. Each card shows the featured image, title, excerpt, and date. Add a breadcrumb navigation at the top: Home > Blog > {Tag Name}.

Copy this prompt to try it in V0

Troubleshooting

API returns 'Unknown Content API Key' or 401 error on all Ghost API calls

Cause: The GHOST_CONTENT_API_KEY environment variable is missing, incorrect, or the key was created for the wrong Ghost integration type (Admin API keys and Content API keys are different).

Solution: Verify your key in Ghost Admin → Integrations. The Content API key is labeled 'Content API key' and is shorter than the Admin API key. Copy it exactly — no leading or trailing spaces. If using Ghost(Pro), ensure your Ghost site is on version 3.0 or higher.

Blog posts render with broken layout — no images, unstyled code blocks, or missing formatting

Cause: Ghost's Koenig editor generates HTML with specific CSS class names (kg-card, kg-image-card, kg-code-card) that require Ghost's stylesheet to render correctly.

Solution: Add the @tailwindcss/typography plugin and apply the prose class to your post content container. For Ghost-specific Koenig card styles, add a ghost-content.css file with Ghost's official Koenig CSS from https://github.com/TryGhost/Ghost/tree/main/ghost/core/core/frontend/src/cards/css

typescript
1// tailwind.config.ts
2module.exports = {
3 plugins: [require('@tailwindcss/typography')],
4};
5
6// In your post page component:
7<div className="prose prose-lg max-w-none" dangerouslySetInnerHTML={{ __html: post.html }} />

Build fails with 'fetch failed' or 'ECONNREFUSED' during next build on Vercel

Cause: Vercel's build process cannot reach your Ghost instance. Either the GHOST_URL is incorrect, your self-hosted Ghost is behind a firewall, or the Ghost server is down.

Solution: Verify your Ghost URL is publicly accessible by opening it in a browser. For self-hosted Ghost, ensure port 443/80 is open and Ghost is running. Check Vercel's build logs for the specific URL that failed. If you have generateStaticParams, add error handling so a failed Ghost fetch returns an empty array rather than crashing the build.

typescript
1export async function generateStaticParams() {
2 try {
3 const posts = await getPosts();
4 return posts.map((post) => ({ slug: post.slug }));
5 } catch {
6 return []; // Build succeeds even if Ghost is unreachable
7 }
8}

Best practices

  • Use Next.js generateStaticParams to pre-render all blog post pages at build time — Ghost content is rarely updated mid-session and static rendering gives the best performance and SEO
  • Add the @tailwindcss/typography plugin and prose class to your Ghost HTML container for automatic styling of all content types including code blocks, blockquotes, and images
  • Set up a Ghost webhook to trigger on-demand ISR revalidation when posts are published — this gives you instant publishing without waiting for ISR timeout intervals
  • Keep GHOST_CONTENT_API_KEY as a server-only variable (no NEXT_PUBLIC_ prefix) even though it's a public read key — this prevents unnecessary exposure in the browser bundle
  • Use Ghost's custom_excerpt field for meta descriptions and card excerpts rather than auto-generated excerpt — custom excerpts are written by authors specifically for preview contexts
  • Handle null feature_image gracefully — not all Ghost posts have cover images, and Next.js Image with a null src throws a build error

Alternatives

Frequently asked questions

Does Ghost work with V0's preview environment on Vercel?

Yes — Ghost's Content API is just a public HTTP endpoint, so Vercel preview deployments can fetch from it the same as production. Just make sure GHOST_URL and GHOST_CONTENT_API_KEY are set for the Preview environment in Vercel Dashboard → Settings → Environment Variables, not just Production.

Can I use Ghost's built-in newsletter with a V0 Next.js frontend?

Ghost's newsletter feature sends emails to members directly from Ghost — this works independently of your frontend. Your V0 app only handles the public-facing blog pages. For membership sign-up, use Ghost's hosted portal (/portal) or integrate Ghost's Member API to create custom sign-up forms that connect to Ghost members.

How do I handle Ghost's membership-gated content in Next.js?

Ghost's Content API only returns content accessible with the public key — gated member-only content is not returned. To show teaser content with a sign-in wall, fetch the post's excerpt from the Content API and redirect authenticated members to Ghost's hosted portal for full content access. Full custom member authentication requires Ghost's Admin API and Member API, which is more complex.

What's the difference between Ghost's Content API and Admin API?

The Content API is read-only and uses a public key — it's designed for frontends and returns published posts, pages, tags, and authors. The Admin API has full read/write access and uses a secret key — it's for backend integrations like importing posts, managing members, or triggering newsletters. For a V0 blog frontend, you only need the Content API.

How do I migrate from WordPress to Ghost for a V0 project?

Ghost has a built-in WordPress importer in Ghost Admin → Labs → Import. It converts WordPress posts, pages, tags, and images to Ghost's format. After importing, create a new Content API key and point your V0 Next.js app at Ghost instead of the WordPress REST API. The main difference in your code is the SDK — replace the WP REST API calls with @tryghost/content-api.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.