Setup Craft CMS
To start, we’ll need to ensure that Craft CMS is set up properly. On an existing craft install, we’ll have to make a few quick changes to ensure our data is readable.
Since we won’t be using the front-end of Craft CMS any more, we can enable headless mode in our config/general.php
.
<?php
return GeneralConfig::create()
// rest of config
->headlessMode(true)
;
Next, we need to set up a route so that our API is publicly accessible. In config/routes.php
add the following:
<?php
return [
'api' => 'graphql/api',
];
That’s it for Craft CMS, onto Next.js!
Setup Next.js
Let’s take this from the top and treat it like we’re setting up a new app.
Install the latest Next.js, in your terminal run the following:
npx create-next-app@latest
Follow the steps to install, as such below (or to your personal preference).
Adding some Variables
Before we can request any data, we’ll want to set up some environment variables that we will store environment specific information in. Either edit or create a .env
file in the root directory and add the following:
CRAFT_CMS_GRAPHQL_TOKEN=YourTokenGoesHere
CRAFT_CMS_GRAPHQL_ENDPOINT=https://your-site.ddev.site/api
Communicating with Craft CMS
In our Next.js directory, make a new file utils/fetchApi.ts
. This will house our logic for fetching our data and will be what we pass our query options to, and add the following:
interface IHeaders {
"Content-Type": string
Authorization: string
}
export async functon fetchApi(
query: string,
variables?: Object | {}
) => {
const headers: IHeaders = {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.CRAFT_CMS_GRAPHQL_TOKEN}`,
}
const res = await fetch(process.env.CRAFT_CMS_GRAPHQL_ENDPOINT, {
method: "POST",
body: JSON.stringify({
query,
variables,
}),
headers: headers
})
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error("Failed to fetch data from API")
}
const json = await res.json()
// All returns are in a data object, we break that down here
return json?.data
}
To explain the above a little, we pass the Bearer token headers here so we can authenticate the API request. If you don’t have any mutators and are just reading data, you can use the default schema and pass a blank token. We still need to pass the Bearer
option to the header though, as without it the API request will fail. For any mutators, you should set up a proper schema and set the token as appropriate.
Once the data has been fetched, we then process the JSON and return the data object.
Writing some Queries
To tell the pages what data we’ll need, we need to write our GraphQL queries. These will be unique per your site, but I’ve included a basic example below.
Create a new file in gql/home.gql.ts
and paste in the following.
export const HOME_QUERY = `
query HomeQuery {
entry(section: "home") {
title
}
}
`
This will return the title from the section in our Craft CMS called “Home”. We export this as a const called HOME_QUERY
so we can use it on our home page, and we’ll import this in a moment to combine it with with the fetchApi
util to grab data.
Pulling it all together
Let’s combine it together.
Open, or create, the file app/page.tsx
and populate it with the following content.
import { HOME_QUERY } from "@/gql/home.gql";
import { fetchApi } from "@/utils/fetchApi";
export default async function Home() {
const { title } = await getData();
return <h1>{title}</h1>;
}
async function getData() {
const { entry } await fetchApi(HOME_QUERY, {});
return entry
}
This will bring in our HOME_QUERY
which we’re currently only using to get the title. We also bring in the utility we use to wrap this and grab it.
Since we’re using the app
directory in Next.js, we then want to utilise the getData()
function to return the fetchAPI
call and grab the data. As we are getting the data from our Craft CMS entry, we can then destructure this so we can access the single variables easier.
Bonus: Working with Images
If you want to utilise next/image
, you’ll run into a couple of issues with remote Images, to mitigate this we need to add it as a remote pattern.
Firstly, let's update our .env
with the following variables:
# NextJS Image remote base
IMAGE_DOMAIN_PROTOCOL=https
IMAGE_DOMAIN_HOSTNAME=your-site.ddev.site
IMAGE_DOMAIN_PATH="/uploads/**"
Update the variables with your domains details, and make sure the IMAGE_DOMAIN_PATH
matches the directory you serve your images from on the Craft CMS side.
Next, open up next.config.mjs
and add the following:
/** @type {import('next').NextConfig} */
const nextConfig = {
// rest of config
images: {
remotePatterns: [
{
protocol: process.env.IMAGE_DOMAIN_PROTOCOL,
hostname: process.env.IMAGE_DOMAIN_HOSTNAME,
port: '',
pathname: process.env.IMAGE_DOMAIN_PATH,
},
],
},
// rest of config
}
Once saved, you can re-run npm run dev
if it doesn’t automatically, and you should now be able to return and render the images.
That’s it, you now have a Next.js app talking to Craft CMS.
Hope that helps!