Converting a Ghost CMS Theme to Next.js with Cursor AI


In this tutorial we’ll take a Ghost CMS theme (built with Handlebars templates) and convert it into a working Next.js site using the Cursor AI coding editor. We assume you know basic HTML/CSS/JavaScript but are new to Next.js. We’ll walk through setting up Cursor, creating a Next.js project, installing the Ghost Content API, and using Cursor’s AI features to transform the Ghost templates ( .hbs
files) into React components. We’ll also fetch live content from Ghost via its Content API and rebuild pages (Home, Post, Tags) with dynamic routing. By the end you’ll have a deployed Next.js version of your Ghost blog, with as much original styling carried over as possible.
1. Install and Configure Cursor
Download and install Cursor AI. Go to cursor.com and download the installer for your OS (Windows, Mac, or Linux). Run the installer and follow the prompts (it works like any standard app) (Cursor AI Code Editor: How to Set Up & Use (Step-by-Step)). Once installed, launch Cursor and sign in (you’ll automatically get a free trial of Cursor Pro).
Import or set up your IDE preferences. On first launch, Cursor can import your VS Code settings so your themes, shortcuts, and extensions carry over (Cursor AI Code Editor: How to Set Up & Use (Step-by-Step)). You can also choose a keyboard shortcut scheme if you used another editor. Enable “Codebase Indexing” so Cursor can scan your project for smarter suggestions.
Enable helpful AI features. Open the Cursor settings (click the gear icon) and under Features turn on Cursor Tab, Suggestions in Comments, and Auto Import (The Perfect Cursor AI setup for React and Next.js). This ensures you get the powerful tab completion and AI-generated comments/code suggestions. It’s also helpful to set the chat mode to “Agent” and enable auto-refresh/scroll in the Chat settings (The Perfect Cursor AI setup for React and Next.js). These tweaks let Cursor autocomplete code (Tab), fix imports automatically, and allow AI edits across files. Now Cursor is ready to help you build a React/Next project.
2. Create a Next.js Project and Add Dependencies
Initialize a new Next.js app. In your terminal (Cursor has an integrated terminal, or use your OS terminal), create a new Next.js project. For example, run:
npx create-next-app@latest ghost-to-next --typescript
This sets up a project named
ghost-to-next
using TypeScript. You can omit--typescript
if you prefer plain JavaScript. Answer the prompts (you can accept defaults). Once done,cd ghost-to-next
into the project folder.Install the Ghost Content API client. Next.js will fetch content from Ghost’s Content API. Ghost provides an official JS client package for this. Install it by running:
npm install @tryghost/content-api
(Or
yarn add @tryghost/content-api
.) This matches the documentation example and ensures you have theGhostContentAPI
class in your code (Content API JavaScript Client). If your Ghost theme uses helpers like string manipulation, you can also install@tryghost/helpers
or@tryghost/string
, but they are optional.Open the project in Cursor. In Cursor, use File > Open Folder (or the command palette) to open
ghost-to-next
. Cursor will index the codebase (if you enabled indexing) and you can now use its AI features on your new Next.js files.
3. Plan the Project Structure
Before coding, map the Ghost theme’s structure to a Next.js layout. A typical Ghost theme looks like this (Ghost Handlebars Theme Structure - Developer Documentation):
ghost-theme/
├── assets/
│ ├── css/
│ │ └── screen.css
│ ├── fonts/
│ ├── images/
│ └── js/
├── default.hbs
├── index.hbs (required)
└── post.hbs (required)
The required Ghost templates are index.hbs
(list of posts) and post.hbs
(single post), with optional partials and a default.hbs
layout (Ghost Handlebars Theme Structure - Developer Documentation). We will mirror this in Next.js as follows:
Pages:
pages/index.jsx
– Home page, will fetch and list recent posts.pages/posts/[slug].jsx
– Dynamic route for individual posts (slug from Ghost).pages/tag/[slug].jsx
– Dynamic tag archive page (optional, for listing posts by tag).pages/_app.jsx
– Global app file (for CSS imports).
Components:
- We’ll create a
components/
folder for reusable parts like header, footer, or post previews (converted from any Ghost partials).
- We’ll create a
Public assets:
Copy the Ghost theme’s assets (especially
screen.css
) intopublic/assets/css/
. For example:ghost-to-next/ ├── pages/ │ ├── index.jsx │ ├── posts/[slug].jsx │ ├── tag/[slug].jsx │ └── _app.jsx ├── components/ ├── public/ │ └── assets/ │ └── css/ │ └── screen.css # copied from Ghost theme ├── .env.local └── package.json
This way we can import screen.css
in our Next app and reuse the original styling.
Planning ahead this structure will make converting templates and fetching data more straightforward. Notice how Ghost’s index.hbs
and post.hbs
will become React components/pages (index.jsx
and [slug].jsx
), and assets from /assets/css
stay in a similar public location.
4. Convert Ghost Templates to React Components with Cursor AI
With the project set up, use Cursor to transform the Ghost Handlebars templates into JSX:
Open a Ghost template in Cursor. For example, open
index.hbs
from your Ghost theme. Select its content, or place your cursor at the top, and either use Cursor Tab (start typing a comment) or Chat (Ctrl+K) to assist. For instance, you might type:// Convert this Ghost Handlebars snippet to React JSX
Then press Tab. Cursor will analyze the code and generate a React version. For example, a Ghost loop like:
{{#foreach posts}} <article> <h2><a href="{{url}}">{{title}}</a></h2> <p>{{excerpt}}</p> </article> {{/foreach}}
could be turned into JSX by Cursor as:
{posts.map(post => ( <article key={post.id}> <h2><Link href={`/posts/${post.slug}`}><a>{post.title}</a></Link></h2> <p>{post.excerpt}</p> </article> ))}
(In practice, you’ll import
Link
fromnext/link
and handle classes/CSS as needed.)Split into components. If
default.hbs
is a layout, you can create aLayout.jsx
component with the common header and footer. Cursor can help here too: copy the common HTML fromdefault.hbs
, select it, and use a comment like// Convert this HTML layout to a React component
and press Tab. Cursor will scaffold a React component.Repeat for other templates. Do the same for
post.hbs
(single post page) and any partials. Cursor’s AI Composer/Agent can intelligently refactor chunks of code, convert loops, conditionals, and inline helpers into React logic. For example, Ghost’s{{#post}} … {{/post}}
helper can become a check onpost
data in React. If you’re stuck, you can also ask Cursor’s Chat (Ctrl+K) questions like “How to convert a Ghost helper to JSX?” or provide an HBS snippet and ask it to rewrite it in React style.
Cursor makes this conversion faster by handling the repetitive parts. As you work, it will auto-add imports (the Auto Import feature) and can even detect lint issues and suggest fixes (if enabled) (The Perfect Cursor AI setup for React and Next.js).
5. Set Up Ghost Environment Variables
To fetch live content from your Ghost site, you need its API URL and a Content API key. In Ghost Admin, go to Integrations and create a new “Custom Integration”. Ghost will show you a Content API Key (a long hex string) and remind you of your API URL (your site’s URL, without a trailing slash). According to Ghost docs:
url
– API domain, must not end in a trailing slash.
key
– hex string copied from the “Integrations” screen in Ghost Admin (Content API JavaScript Client).
In your Next.js project, create a file .env.local
at the root and add:
NEXT_PUBLIC_GHOST_API_URL=https://your-ghost-site.com
NEXT_PUBLIC_GHOST_CONTENT_API_KEY=your_content_api_key_here
Using the NEXT_PUBLIC_
prefix ensures these variables are available in the browser (Next will embed them in the client bundle). These variables match the Ghost client’s options (url
and key
). We do not commit .env.local
to Git, so add it to .gitignore
. Now your code can read the Ghost URL and key via process.env.NEXT_PUBLIC_GHOST_API_URL
and process.env.NEXT_PUBLIC_GHOST_CONTENT_API_KEY
.
6. Fetch Ghost Content with the Content API
Next.js can fetch Ghost’s data at build time or on-demand. We’ll use the official client library (@tryghost/content-api
) we installed. In any page or component, import it and initialize it with our env vars:
import GhostContentAPI from '@tryghost/content-api';
const api = new GhostContentAPI({
url: process.env.NEXT_PUBLIC_GHOST_API_URL,
key: process.env.NEXT_PUBLIC_GHOST_CONTENT_API_KEY,
version: 'v5.0'
});
This matches the Ghost documentation example for creating the client (Content API JavaScript Client). With api
ready, you can call its methods like api.posts.browse()
, api.posts.read()
, api.tags.browse()
, etc. For example, to get the latest posts on the home page:
export async function getStaticProps() {
const posts = await api.posts.browse({ include: 'tags,authors' });
return { props: { posts } };
}
This uses api.posts.browse({limit: n})
to fetch many posts (Ghost uses pagination). The documentation shows a similar example of browsing 5 posts and iterating titles (Content API JavaScript Client). In our case, we return the data as props for React to render. We can also fetch pages (api.pages.browse()
and api.pages.read({slug})
) or tags (api.tags.browse()
) the same way.
(Cursor’s AI can help write these functions too: you could type a comment like // fetch all posts from Ghost API
and press Tab to get boilerplate code.)
7. Build the Home, Post, and Tag Pages
Now we create the actual Next.js pages and use the data:
Home page (
pages/index.jsx
): UsegetStaticProps
to fetch recent posts (as above) and render them in a list or grid. You can use the JSX we converted earlier. For example, map overposts
and output titles, excerpts, and links to each post’s page (/posts/[slug]
).Post detail page (
pages/posts/[slug].jsx
): This is a dynamic route file. WritegetStaticPaths()
to pre-generate pages for each post. For example:export async function getStaticPaths() { const posts = await api.posts.browse({limit: 1000}); const paths = posts.map(post => ({ params: { slug: post.slug } })); return { paths, fallback: false }; }
Then
getStaticProps({ params })
can fetch the single post by slug:export async function getStaticProps({ params }) { const post = await api.posts.read({ slug: params.slug }); return { props: { post } }; }
Inside the component, render the post’s title and content. Ghost post content is usually returned in HTML form, so you might render it with
dangerouslySetInnerHTML
inside a container (after sanitizing if needed). Otherwise, use the structured data (post.html
,post.title
, etc.).Tag page (
pages/tag/[slug].jsx
): If your theme had tag archives, do the same:getStaticPaths()
withapi.tags.browse()
to get all tag slugs, then ingetStaticProps
useapi.posts.browse({filter:
tag:${params.slug}})
orapi.tags.read({slug: params.slug, include: 'count.posts'})
to find posts for that tag. Render the tag’s name and its posts.
Cursor can assist: For example, place a comment // Generate getStaticPaths for posts slugs
and press Tab; it will often generate the loop and return statement automatically. Cursor’s AI is aware of Next’s conventions, so it can scaffold these functions if you hint at them.
8. Handle Routing and Links
In your JSX, use Next.js’s <Link>
component for navigation. For example, on the home page use:
import Link from 'next/link';
// ...
<Link href={`/posts/${post.slug}`}><a>{post.title}</a></Link>
In dynamic pages, the filename [slug].jsx
means Next.js will route /posts/my-post-slug
to that component. Similarly, pages under pages/tag/[slug].jsx
will handle /tag/technology
, for example. Because we set fallback: false
, any slug not returned by our API paths will give a 404. This matches the Ghost theme’s behavior (only existing posts/tags have pages).
9. Apply Ghost Theme Styling
We want to reuse the Ghost theme’s CSS. Earlier, we copied screen.css
into public/assets/css/screen.css
. Now import it in pages/_app.jsx
or in individual pages:
import '../public/assets/css/screen.css';
This applies the original theme’s styles site-wide. You may need to adjust class names: Ghost themes often use semantic class names or none at all. In your converted JSX, check the HTML elements and classes (from default.hbs
, post.hbs
, etc.) and ensure they match the CSS rules in screen.css
. Cursor AI can help here too: if you notice a missing class, comment something like // add className="site-title" to header element
and use Tab to let Cursor fix it.
Because we placed CSS under public/assets/css
, remember to update any paths to images or fonts inside screen.css
if needed (paths may need adjusting to Next’s public/
structure).
By importing the CSS, much of the original look & feel carries over without rewriting styles. For any interactive components (menus, etc.), make sure any needed scripts are also added (for example, Ghost themes sometimes use JavaScript for menu toggles).
10. Test Locally and Deploy
Test your app. Run
npm run dev
to start the Next.js development server. Open http://localhost:3000 and browse the pages. Check that the home page shows posts, links work, and individual post pages display correct content. Fix any errors (Cursor can help debug – for example, use the AI Console to ask why something isn’t working).Build for production. Run
npm run build
andnpm run start
to make sure a production build works. This compiles static pages using yourgetStaticProps
logic.Deploy online. Commit your code to GitHub and connect to a hosting service like Vercel (which has first-class Next.js support) or Netlify. On Vercel, you can set the same environment variables (
NEXT_PUBLIC_GHOST_API_URL
andNEXT_PUBLIC_GHOST_CONTENT_API_KEY
) in the project’s dashboard. When you deploy, Vercel will build the site and publish it.Final checks. Verify on the deployed URL that the site matches your Ghost theme’s style and that content loads from Ghost. Your Next.js site is now a headless front-end for Ghost CMS.
With these steps, you’ve successfully converted a Ghost Handlebars theme into a Next.js project using Cursor’s AI-powered assistance. You set up Cursor for React/Next development (The Perfect Cursor AI setup for React and Next.js) (Cursor AI Code Editor: How to Set Up & Use (Step-by-Step)), installed Next.js and the Ghost Content API client (Content API JavaScript Client), planned the file structure (mirroring index.hbs
→ pages/index.jsx
, etc.) (Ghost Handlebars Theme Structure - Developer Documentation), and used Cursor to transform templates to JSX. You stored your Ghost API URL/key in environment variables (Content API JavaScript Client), fetched posts/tags via the Content API (Content API JavaScript Client), and built dynamic pages with Next.js routing. Finally, you applied the original CSS so the new site looks like your Ghost theme. Congratulations on modernizing your blog front-end with Next.js and Cursor!
How to install Ghost CMS with Cloudflare worker with domain sub-directory - topics - Tenten AI
Headless Ghost CMS 和 Next.js 相關的 GitHub Repo - topics - Tenten AI
解鎖極致效能:Headless Ghost CMS 與 Vercel 整合的藍圖 - topics - Tenten AI
當 Headless Ghost CMS 的靈活遇上 Vercel 的速度:部署的藝術 - topics - Tenten AI
Setting up a headless Ghost CMS with Next.js | Steve Perry Creative
Subscribe to my newsletter
Read articles from Erik Chen directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
