2024/10/18

Using Craft CMS Live Preview with Next.js

Craft CMSHeadlessNext.js

To get live preview working, we will need to make changes in two places. First, we’ll update our Next.js app with the appropriate queries, followed by a few small changes to our Craft CMS set up.

If you haven’t set up Craft CMS with Next.js already, I advise you read through my previous article before starting this one.

Updating Next.js

In your Next.js .env file, set up some new variables we will use.

# match your API endpoint URL here
CRAFT_CMS_GRAPHQL_ENDPOINT=http://your-site.com/api

# not always needed, but added in case you use a limited scope.
CRAFT_CMS_GRAPHQL_TOKEN=

In a utility file (/util/getApiQuery.ts), let’s set up the code for talking to our GraphQL endpoint and ensuring that it returns the appropriate data.

interface IHeaders {
  "Content-Type": string
    Authorization: string
    [k: string]: any
}

export async function getApiQuery(
  query: string,
  variables?: Object | {},
  preview?: string | string[] | undefined,
) {
    const endpoints = `${process.env.CRAFT_CMS_GRAPHQL_ENDPOINT}`
    
    // If we have a preview token, adapt our
    // endpoint to feature the x-craft-live-preview stucture.
    const src = preview ? `${endpoints}?x-craft-live-preview=${preview}` : endpoint
    
    // Set the necessary headers
    const headers: IHeaders = {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.CRAFT_CMS_GRAPHQL_TOKEN}`,
    }
    
    // If we're on a preview, set the craft token header which
    // is pulled from the query string
    if (preview) {
        headers['x-craft-token'] = preview
    }
    
    // Handle the fetch
    const res = await fetch(src, {
    method: "POST",
    body: JSON.stringify({
      query,
      variables,
    }),
    headers: headers
  })
  
  if (!res.ok) {
    throw new Error("Failed to fetch data from API")
  }
  
  const json = await res.json()
  return json?.data
}

If you already have a method of fetching data, the important parts here are dynamically setting our endpoint.

const endpoints = `${process.env.CRAFT_CMS_GRAPHQL_ENDPOINT}`
const src = preview ? `${endpoints}?x-craft-live-preview=${preview}` : endpoint

And, updating our headers.

const headers: IHeaders = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.CRAFT_CMS_GRAPHQL_TOKEN}`,
}

if (preview) {
    headers['x-craft-token'] = preview
}

Next, inside of one of our pages, e.g. /app/page.tsx, we need to fetch any of the search parameters to ensure we can check for the token. We can do this by updating our main function.

If the searchParams[‘token’] exists, and if not, set it to a blank string. Then, we pass it through to a getData function.

const Page = async ({
    searchParams,
}: {
    searchParams: { [key: string]: string | string[] | undefined }
}) => {
    const preview = searchParams['token'] || ''
    const { title } = await getData(preview)
}) {
    return <>{title}</>
}

export default Page

Inside our getData function, we need to make sure we are passing through the appropriate preview (token) string.

To do this, we will pass it through to our getApiQuery function we created earlier, and everything else will be handled there.

async function getData(preview?: string | string[] | undefined) {
    // Your GraphQL query, ideally stored in a seperate file.
    const query = `
        query AboutQuery {
            aboutEntries {
                title
            }
        }
    `
    
    // Example call passing.
    const { aboutEntries } = await getApiQuery(query, {}, preview)
  
    // Return the data
    return aboutEntries[0]
}

Updating Craft CMS

To ensure our preview URLs are being sent over to the right place, we will need to tweak each section’s preview targets.

Let’s set up a few alias so we can update these appropriately per environment. In config/general.php update the aliases:

<?php

use craft\config\GeneralConfig;

return GeneralConfig::create()
    // rest of your code here
    ->aliases([
        '@webroot' => dirname(__DIR__) . '/web',
        '@preview' => getenv('PREVIEW_URL'),
    ]);

In our Craft .env add the following:

PREVIEW_URL=https://localhost:3000

Then, in each section you want to enable Live Preview for, update the preview targets to reference the alias we just made, like so {{ alias('@preview') }}/{uri}.

That’s it for the Craft side. Now, if you head to an entry, you should be able to click “Live Preview” and see everything rendering.

Round up

Perfect, so just to clarify a little about everything that we have been through:

  • When our site is requested through Craft’s live preview, it will pass through a couple of query parameters to the URL it calls, one which is token.
  • We’ll then use these to define the way we fetch the data from the endpoint, by conditionally rendering extra headers and updating our source URL.
  • The data will be loaded into our view and render the content as appropriate, because Craft handles the rest on the GraphQL side.

Hope this helps you, if you have any issues let me know on Mastodon.

Comments

Comment on this blog post by publicly replying to this Mastodon post using a Mastodon or other ActivityPub/​Fediverse account.