Learn how to spin up a JavaScript app using Ghost as a headless CMS and build a completely custom front-end with the Next.js React framework.

The following sections cover how to source content from Ghost and feed it directly into a web app with Next.js.

Diagram of Ghost providing content to Next.js via the Content API


This configuration of a Ghost publication requires existing moderate knowledge of JavaScript and React. You'll need an active Ghost account to get started, which can either be self-hosted or using Ghost(Pro).

Additionally, you'll need to setup a React & Next.js application via the command line:

yarn create next-app
cd my-next-app
yarn dev

Next.js can also be setup manually – refer to the official Next.js documentation for more information.

Getting started

Thanks to the JavaScript Content API Client Library, it's possible for content from a Ghost site can be directly accessed within a Next.js application.

Create a new file called posts.js within an api/ directory. This file will contain all the functions needed to request Ghost post content, as well as an instance of the Ghost Content API.

Install the official JavaScript Ghost Content API helper using:

yarn add @tryghost/content-api

Once the helper is installed it can be added to the posts.js file using a static import statement:

import GhostContentAPI from "@tryghost/content-api";

Now an instance of the Ghost Content API can be created using Ghost site credentials:

import GhostContentAPI from "@tryghost/content-api";

// Create API instance with site credentials
const api = new GhostContentAPI({
  url: 'https://demo.ghost.io',
  key: '22444f78447824223cefc48062',
  version: "v2"

Change the url value to the URL of the Ghost site. For Ghost(Pro) customers, this is the Ghost URL ending in .ghost.io, and for people using the self-hosted version of Ghost, it’s the same URL used to view the admin panel.

Create a custom integration within Ghost Admin to generate a key and change the key value.

Ghost Admin new Integration view

For more detailed steps on setting up Integrations check out our documentation on the Content API.

Exposing content

The posts.browse() endpoint can be used to get all the posts from a Ghost site. This can be done with the following code as an asynchronous function:

export async function getPosts() {
  return await api.posts
      limit: "all"
    .catch(err => {

Using an asynchronous function means Next.js will wait until all the content has been retrieved from Ghost before loading the page. The export function means your content will be available throughout the application.

Rendering posts

Since you're sending content from Ghost to a React application, data is passed to pages and components with props. Next.js extends upon that concept with getInitialProps. This function will load the Ghost site content into the page before it's rendered in the browser.

Use the following to import the getPosts function created in previous steps within a page you want to render Ghost posts:

import { getPosts } from '../api/posts';

The posts can be provided as props to the page with the getInitialProps function. In this case the page is an IndexPage:

IndexPage.getInitialProps = async () => {
  const posts = await getPosts();
  return { posts: posts }

Now the posts can be used within the IndexPage via the component props:

const IndexPage = (props) => (
    {props.posts.map(post => (
      <li key={post.id}>{post.title}</li>

Pages in Next.js are stored in a pages/ directory. To find out more about how pages work check out the official documentation.

Rendering a single post

Retrieving Ghost content from a single post can be done in a similar fashion to retrieving all posts. By using posts.read() it's possible to query the Ghost Content API for a particular post using a post id or slug.

Reopen the api/posts.js file and add the following async function:

export async function getSinglePost(postSlug) {
  return await api.posts
      slug: postSlug
    .catch(err => {

This function accepts a single postSlug parameter, which will be passed down by the template file using it. The page slug can then be used to query the Ghost Content API and get the associated post data back.

Next.js provides dynamic routing for pages that don't have a fixed URL / slug. The name of the js file will be the variable, in this case the post slug, wrapped in square brackets – [slug].js.

The getSinglePost() function can be used within the [slug].js file like so:

// Import getSinglePost function
import { getSinglePost } from '../api/posts';

// PostPage page component
const PostPage = (props) => {
  // Render post title and content in the page from props
  return (
      <div dangerouslySetInnerHTML={{ __html: props.post.html }} />

// Pass the page slug over to the "getSinglePost" function
// In turn passing it to the posts.read() to query the Ghost Content API
PostPage.getInitialProps = async (params) => {
  const post = await getSinglePost(params.query.slug);
  return { post: post }

Pages can be linked to with the Next.js <Link/> component. Calling it can be done with:

import Link from 'next/link';

The Link component is used like so:

const IndexPage = (props) => (
    {props.posts.map(post => (
      <li key={post.id}>
        <Link href={`/[slug]`} as={`/${post.slug}`}>

Pages are linked in this fashion within Next.js applications to make full use of client-side rendering as well as server-side rendering. To read more about how the Link component works and it's use within Next.js apps check out their documentation.

Next steps

You should have now covered the steps to retrieve posts from the Ghost Content API and sending your content to your Next.js site. Take a look at our Recipes for Next.js for examples of how to extend this further by generating content pages, author pages or exposing post attributes.

Don't forget to refer to the official Next.js documentation as well as their learning resources to get a greater understanding of the framework.