Skip to content
Menu

Using Craft CMS Live Preview with Next.js

Craft CMS has a great Live Preview feature, which allows you to view the entry you are currently editing in-situ on the website before publishing it. As you make changes, the preview of the website changes in real-time.

This works out-of-the-box if your website templates are using Twig, which is built into Craft CMS, but if you're showing your content on another frontend - in our case, a Next.js application - it requires another few steps.

Note: this technique is designed around hosting a Next.js website on Vercel. Other hosting providers may operate differently.

Step One: The Preview API

Your Next.js application needs to have an API that can listen to requests from Live Preview, and then routes them back to the right template.

Create a file in your pages directory called api/preview.js

export default async (req, res) => {
if (req.query.token === null) {
return res.status(401).json({ message: 'No preview token' })
}
if (req.query.uri === null) {
return res.status(401).json({ message: 'No URI provided' })
}
res.setPreviewData(
{
token: req.query.token ?? null,
},
{
maxAge: 60,
}
)
res.redirect(`/${req.query.uri}`)
}

This API will check that a preview token (which is automatically provided by Craft CMS) is present and that a valid URI is provided (we'll get to this in a moment).

It then uses setPreviewData to remember the preview token so that pages can load the correct data and puts a maximum age on that of 60 seconds. You can change that to whatever feels appropriate, and it's worth noting that within that duration the user would be on "preview mode" so you may wish to keep this very short to avoid confusion.

The final line redirects the user to the URI requested.

Step 2: Updating your page queries

Your API unlocks Next's Preview Mode functionality.

Each of your page queries must now be updated to be aware of the preview mode and its stored settings.

From the context variable, you can access preview and previewData.

  • preview is a boolean, telling you if the user is in preview mode or not
  • previewData is an object of the preview data we set on our API (in our case, that includes the token from Craft)

Depending on your coding style, it might look a bit like one of these…

export const getStaticProps = async ({ params, preview, previewData }) => {
// Your stuff goes here
}
// OR
export const getStaticProps = async (context) => {
// Your stuff goes here
const preview = context.preview;
const previewData = context.preview;
}

This leads us to the bit that will be different for every developer. With your GraphQL query, you need to configure it to append the token to the URL.

So if your API is normally at https://cms.website.com/api you would want it to become something like https://cms.website.com/api?token=RANDOMTOKENGOESHERE

This makes Craft CMS realise what version of the content you want to see (the preview content) and will serve that instead of the published content, which GraphQL would usually serve.

We use Apollo Client for our GraphQL queries, and we've updated our ApolloClient function to accept an optional previewToken property which, if defined, appends that to the URI we use for Apollo…

const apolloClient = (
previewToken,
) => {
const uri = `${process.env.NEXT_PUBLIC_CRAFT_GRAPHQL_URL}${
previewToken ? `?token=${previewToken}` : ``
}`
// The rest of the Apollo Client goes here
}

On the pages, it means that when we're now able to access the token from previewData and then pass that into our Apollo Client…

const previewToken = preview ? previewData.token : undefined
const { data } = await apolloClient(previewToken).query({
query: gql`
query BlogSingleQuery($slug: [String], $site: [String]) {
entry(section: "blogPost", slug: $slug, site: $site) {
... on blogPost_blogPost_Entry {
title
slug
}
}
}
`,
})

Step Three: Configuring Craft

The final part is to configure Craft's Preview Targets to use our new API.

When editing a section in the CMS, you will see a section called "Preview Targets". Add a row and in the Label give it a descriptive name, such as "Next.js Frontend" and the URL format should be an absolute link to your API, followed by the URL to it.

So for example, if you have a blog post that should be accessible at https://www.website.com/blog/{slug} you would want to set the URL format to https://www.website.com/api?uri=blog/{slug}, or for the homepage simply https://www.website.com/api?uri=.

Note: there's no preceding slash, but if you'd prefer there to be so you could just remove the hard-coded slash on res.redirect in your API.

Now when you use Live Preview, you will be able to preview your content in real-time.

Notes and Caveats

This is mostly taking advantage of the Preview Targets built-in to Craft CMS, and the Preview Mode built-in to Next.js. The two are a perfect match but require a little configuration. Regrettably, everybody would have a different approach for linking Next.js to Craft's GraphQL API and so the perhaps most difficult part is the part I give the least guidance on. But feel free to reach out to me on andrew@madebymutual.com and I'll be happy to try to help with your specific setup.

One downside is that we've found this often doesn't work when running locally. This appears to a problem with cookies being set in iframes when they're not using HTTPS (and the local Next dev server is HTTP). If anybody finds a solution to this, I'd love to hear it!