# 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

1. **Download and install Cursor AI.** Go to [cursor.com](https://www.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)](https://www.usesaaskit.com/blog/cursor-ai-code-editor-setup-guide#:~:text=1)). Once installed, launch Cursor and sign in (you’ll automatically get a free trial of Cursor Pro).
    
2. **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)](https://www.usesaaskit.com/blog/cursor-ai-code-editor-setup-guide#:~:text=5,Code%20Settings)). 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.
    
3. **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](https://www.builder.io/blog/cursor-ai-tips-react-nextjs#:~:text=,Auto%20Import)). 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](https://www.builder.io/blog/cursor-ai-tips-react-nextjs#:~:text=,messages%20in%20the%20chat%20window)). 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

1. **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:
    
    ```bash
    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.
    
2. **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:
    
    ```bash
    npm install @tryghost/content-api
    ```
    
    (Or `yarn add @tryghost/content-api`.) This matches the documentation example and ensures you have the `GhostContentAPI` class in your code ([Content API JavaScript Client](https://ghost.org/docs/content-api/javascript/#:~:text=%60yarn%20add%20%40tryghost%2Fcontent)). If your Ghost theme uses helpers like string manipulation, you can also install `@tryghost/helpers` or `@tryghost/string`, but they are optional.
    
3. **Open the project in Cursor.** In Cursor, use **File &gt; 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](https://ghost.org/docs/themes/structure/#:~:text=.%20%E2%94%9C%E2%94%80%E2%94%80%20%2Fassets%20,required)):

```plaintext
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](https://ghost.org/docs/themes/structure/#:~:text=.%20%E2%94%9C%E2%94%80%E2%94%80%20%2Fassets%20,required)). 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).
        
* **Public assets:**
    
    * Copy the Ghost theme’s assets (especially `screen.css`) into `public/assets/css/`. For example:
        
        ```plaintext
        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:
    
    ```js
    // 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:
    
    ```plaintext
    {{#foreach posts}}
      <article>
        <h2><a href="{{url}}">{{title}}</a></h2>
        <p>{{excerpt}}</p>
      </article>
    {{/foreach}}
    ```
    
    could be turned into JSX by Cursor as:
    
    ```jsx
    {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` from `next/link` and handle classes/CSS as needed.)
    
* **Split into components.** If `default.hbs` is a layout, you can create a `Layout.jsx` component with the common header and footer. Cursor can help here too: copy the common HTML from `default.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 on `post` 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](https://www.builder.io/blog/cursor-ai-tips-react-nextjs#:~:text=,Auto%20Import)).

## 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](https://ghost.org/docs/content-api/javascript/#:~:text=%2A%20%60url%60%20,should%20be%20set%20to%20%E2%80%98v3%E2%80%99)).
>     

In your Next.js project, create a file `.env.local` at the root and add:

```plaintext
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:

```js
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](https://ghost.org/docs/content-api/javascript/#:~:text=const%20api%20%3D%20new%20GhostContentAPI%28,)). 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:

```js
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](https://ghost.org/docs/content-api/javascript/#:~:text=const%20api%20%3D%20new%20GhostContentAPI%28,)). 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`): Use `getStaticProps` 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 over `posts` 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](https://nextjs.org/docs/routing/dynamic-routes) file. Write `getStaticPaths()` to pre-generate pages for each post. For example:
    
    ```js
    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:
    
    ```js
    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()` with `api.tags.browse()` to get all tag slugs, then in `getStaticProps` use `api.posts.browse({filter:` tag:${params.slug}`})` or `api.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:

```jsx
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:

```jsx
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

1. **Test your app.** Run `npm run dev` to start the Next.js development server. Open [http://localhost:3000](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).
    
2. **Build for production.** Run `npm run build` and `npm run start` to make sure a production build works. This compiles static pages using your `getStaticProps` logic.
    
3. **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` and `NEXT_PUBLIC_GHOST_CONTENT_API_KEY`) in the project’s dashboard. When you deploy, Vercel will build the site and publish it.
    
4. **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](https://www.builder.io/blog/cursor-ai-tips-react-nextjs#:~:text=,Auto%20Import)) ([Cursor AI Code Editor: How to Set Up & Use (Step-by-Step)](https://www.usesaaskit.com/blog/cursor-ai-code-editor-setup-guide#:~:text=1)), installed Next.js and the Ghost Content API client ([Content API JavaScript Client](https://ghost.org/docs/content-api/javascript/#:~:text=%60yarn%20add%20%40tryghost%2Fcontent)), planned the file structure (mirroring `index.hbs` → `pages/index.jsx`, etc.) ([Ghost Handlebars Theme Structure - Developer Documentation](https://ghost.org/docs/themes/structure/#:~:text=.%20%E2%94%9C%E2%94%80%E2%94%80%20%2Fassets%20,required)), and used Cursor to transform templates to JSX. You stored your Ghost API URL/key in environment variables ([Content API JavaScript Client](https://ghost.org/docs/content-api/javascript/#:~:text=%2A%20%60url%60%20,should%20be%20set%20to%20%E2%80%98v3%E2%80%99)), fetched posts/tags via the Content API ([Content API JavaScript Client](https://ghost.org/docs/content-api/javascript/#:~:text=const%20api%20%3D%20new%20GhostContentAPI%28,)), 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!

---

* [Latest ghost topics - Tenten AI](https://university.tenten.co/tag/ghost)
    
* [How to install Ghost CMS with Cloudflare worker with domain sub-directory - topics - Tenten AI](https://university.tenten.co/t/how-to-install-ghost-cms-with-cloudflare-worker-with-domain-sub-directory/1536)
    
* [Headless Ghost CMS 和 Next.js 相關的 GitHub Repo - topics - Tenten AI](https://university.tenten.co/t/headless-ghost-cms-next-js-github-repo/1772)
    
* [解鎖極致效能：Headless Ghost CMS 與 Vercel 整合的藍圖 - topics - Tenten AI](https://university.tenten.co/t/headless-ghost-cms-vercel/1770/2)
    
* [使用 Cursor AI 轉換 Ghost CMS 樣板到 Next.js - topics - Tenten AI](https://university.tenten.co/t/cursor-ai-ghost-cms-next-js/1769)
    
* [當 Headless Ghost CMS 的靈活遇上 Vercel 的速度：部署的藝術 - topics - Tenten AI](https://university.tenten.co/t/headless-ghost-cms-vercel/1768)
    
* [將 Ghost CMS 與 Vercel (Next.js) 整合 - topics - Tenten AI](https://university.tenten.co/t/ghost-cms-vercel-next-js/1765/2)
    
* [Tenten - AI / ML Development](https://developer.tenten.co/)
    
* [Host Ghost CMS on Vercel Easily](https://developer.tenten.co/how-to-make-ghost-cms-host-with-vercel)
    
* [ghost cms vercel - Google Search](https://www.google.com/search?q=ghost+cms+vercel&oq=ghost+cms+vercel&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIICAEQABgWGB4yCggCEAAYgAQYogQyBwgDEAAY7wUyBggEEEUYPDIGCAUQRRg8MgYIBhBFGDzSAQgzNTI0ajBqN6gCALACAA&sourceid=chrome&ie=UTF-8&num=100)
    
* [將 Ghost CMS 與 Vercel (Next.js) 整合 - topics - Tenten AI](https://university.tenten.co/t/ghost-cms-vercel-next-js/1765)
    
* [How to run Ghost CMS on Vercel - topics - Tenten AI](https://university.tenten.co/t/how-to-run-ghost-cms-on-vercel/1625)
    
* [shishak/next-cms-ghost-vercel](https://github.com/shishak/next-cms-ghost-vercel)
    
* [Setting up a headless Ghost CMS with Next.js | Steve Perry Creative](https://steveperrycreative.com/posts/setting-up-a-headless-ghost-cms-with-next-js)
