How to set up a Headless Craft CMS with NextJS

by Daniel Zuccon


Craft CMS introduced a native GraphQL API when they shipped version 3.3. This created the perfect interface to go headless with Craft CMS! Not only does it enable your site to be more secure by hiding your admin area, but it also gives you more control in terms of scalability and determining how you want to display your content.

In this guide, we’ll go into how to set up a Craft CMS backend with a NextJS frontend. However, you aren’t limited to using NextJS. You can choose your own frontend flavour such as Nuxt. We’re using NextJS as it has a lot of the good stuff built into it already.


  1. Craft CMS running on Amezmo
  2. NextJS running on Vercel

Note: We have the Craft CMS backend and the NextJS frontend in their own separate git repositories, but you could run them as monorepo if you needed.



There’s also the lesser-known Headless Mode flag which optimises your Control Panel for sites with no Craft frontend. You can turn this on if it sounds useful for you.

Preparing the Backend

1. Enable GraphQL schema:

Once Craft CMS is running, you need to now enable GraphQl Schema.

Here you can choose which elements out of your CMS you want to be accessible via GraphQL.

Edit the public GraphQL schema

To get started you can just enable everything, but it is always best to trim down that scope before going into production.

3. Create a new route

Now you need to create a new route in Craft CMS so that your headless frontend has an endpoint to retrieve the data from.

You can call your route anything but something like /api is fairly standard.

You just need to set up a new template in /config/routes.php with the following code to tell Craft to return only GraphQL for it:

// /config/routes.php
return [
        'api' => 'graphql/api',
        // ...

4. Setup Webhooks

Your next question is probably, but how does my website update when Sally in Marketing publishes a new blog?

Luckily, Craft CMS created a first-party Webhooks plugin ( since V3 that takes care of all of this for you.

You need to fire the afterSave event:

After save event

And to prevent Craft CMS from rapid firing new frontend builds every time someone press save, you can dial in when it should and shouldn’t fire:

We’re using Vercel (the native hosting platform for NextJS) to host our frontend, but you can plug in your required host endpoint here:

Host endpoint

Bonus tip!

If your GraphQL game isn’t that strong yet, Craft CMS also ships with the GraphiQL interface accessible via /admin/graphiql.

This makes it super easy to find the specific entries you want to expose over GraphQL. Then it’s basically copy and paste into your frontend code later.

GraphiQL interface

You can test your Craft CMS endpoint by hitting your /api route with something like Insomnia and see if it returns the expected data.

Now that your backend is working as intended, it’s time to move on to that frontend.

Handling the frontend

5. Set up environmental variables

It’s best practice to never commit any API Keys in your code.

So move them into environmental variables locally and access via process.env, and also add them directly to Vercel.

6. Set up a fetch() function

NextJS version 13 ships with the fetch() API. It also does request de-duping out of the box to make your application super efficient.

First we’ll set up a function in /app/lib/craft.js which we can re-use to pull in data from Craft CMS across our site.

// /app/lib/craft.js
export async function fetchData(graphql) {
    let craftUrl = process.env.CRAFT_URL;

    const res = await fetch(craftUrl, {
        method: 'post',
        body: graphql,
        headers: {
            'Content-Type': 'application/graphql',
            'Authorization': `Bearer fAkEaPiKeYfAkEaPiKeYfAkEaPiKeYfAkEaPiKeY`

    if (res.status !== 200) {
        throw new Error('Failed to fetch API');

    const json = await res.json();

    if (json.errors) {
        throw new Error('Failed to fetch API');


Now from anywhere in our pages or components, we can pull in some juicy data:

// /pages/blog.js
import { fetchData } from "./lib/craft.js";

async function getBlogs() {
  const data = await fetchData(`
      categories(group: "Types", orderBy: "title") {

  return data.blogs;

export default async function RootLayout({ children }) {
  const blogsData = await getBlogs();

  return (
    { => (
        <li key={}><Link href={`types/${blog.slug}`}">{blog.title}</Link></li>

2. Watch deployment in action!

Once this is tested and deployed, you can try saving an entry in Craft CMS, and watch the deployment take place! So, now what? You’ve successfully set up a headless Craft CMS with NextJS! Now you can enjoy the benefits of a website that is headless, including tighter security and more control over the aesthetics.

This guide was written by Daniel Zuccon from