How to set up a Headless Craft CMS with NextJS
Introduction
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.
Prerequisites
- Craft CMS running on Amezmo
- 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.
Steps
Optional
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.

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 (https://plugins.craftcms.com/webhooks) since V3 that takes care of all of this for you.
You need to fire the afterSave
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:

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.

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) {
console.error(json.errors);
throw new Error('Failed to fetch API');
}
return json.data;
}
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") {
id
slug
title
}
}
`);
return data.blogs;
}
export default async function RootLayout({ children }) {
const blogsData = await getBlogs();
return (
<ul>
{blogsData.map((blog) => (
<li key={blog.id}><Link href={`types/${blog.slug}`}">{blog.title}</Link></li>
))}
</ul>
)
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 Optimising.com.au.