Craft CMS Live Preview with Next.js 9.3 Static Site Generation and GraphQL
Tags: craftcms, nextjs
Published: 2020-03-15 18:42:24

[Next.js 9.3](https://nextjs.org/blog/next-9-3) was released 9th of March and has a new and exciting feature for all headless CMS users out there: **Static Site Generation** coupled with **Preview Mode**

[Static Site Generation](https://nextjs.org/blog/next-9-3#next-gen-static-site-generation-ssg-support) (SSG for short) allows us to generate a static site with the contents of our headless CMS during the build. The magic function which enables this is `getStaticProps`. There we can fetch data from any data source and Next.js will use it to generate a static site.

So this is nice, but now you have static HTML/JS/CSS, but your users of the CMS might want to see a preview of their newly created content. Then you want to dynamically render the page again so that the CMS editor can see his changes.

So I tried to combine a headless Craft CMS with a frontend written in Next.js with static site generation and working live preview. The data fetching will happen over the new GraphQL API of Craft.

First of all, you need a working Craft CMS 3.4 installation with activated  GraphQL API. See the [Installation guide](https://docs.craftcms.com/v3/installation.html) and the GraphQL [Getting Started guide](https://docs.craftcms.com/v3/graphql.html#getting-started) for instructions.
Then you also need a running Next.js website. See the [Getting Started guide](https://nextjs.org/docs/getting-started).

# Configuration in Next.js

I have implemented everything in TypeScript, so if you use JavaScript, you have to adjust the examples a bit.

This a simple fetch wrapper to be able to make GraphQL Requests to Craft:

*utils/fetch.ts*
```typescript
import fetch from 'node-fetch'; // install with `npm install node-fetch`

/**
 * Fetch from Craft GraphQL API
 * @param query GraphQL Query
 * @param previewToken Optional preview token to get draft data
 */
export default async function(
  query: string,
  previewToken?: string
) {
  let craftUrl = 'http://localhost:8888/api';

  if (previewToken && previewToken !== '') {
    craftUrl += '?token=' + previewToken;
  }

  const res = await fetch(craftUrl, {
    method: 'post',
    body: query,
    headers: {
      'Content-Type': 'application/graphql',
    },
  });

  return await res.json();
}
```

In the page file, where you want to have SSG with a preview, you have to implement the [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) function.

This fetches the data for the Homepage. In my case this is a single section called "Homepage", which has a field named "text".

*pages/index.tsx*
```typescript
export const getStaticProps: GetStaticProps = async context => {
  const { data } = await fetch(
    `
    {
      entry(title: "Homepage") {
        ... on homepage_homepage_Entry {
          text
        }
      }
    }
    `,
    context.preview ? context.previewData?.previewToken : undefined
  );

  return {
    props: {
      data
    }
  };
};
```

The `context.previewData.previewToken` data is coming from an API route which is shown below. We only supply the previewToken to the fetch function when the `context.preview` field is `true`.

*pages/api/preview.ts*
```typescript
import { NextApiRequest, NextApiResponse } from "next";
import fetch from "../../utils/fetch";

export default async (req: NextApiRequest, res: NextApiResponse) => {
  // (1)
  // Check for the right query params
  if (!req.query["x-craft-live-preview"] || !req.query.entryUid) {
    return res
      .status(401)
      .json({ message: "Not allowed to access this route" });
  }

  // (2)
  // Get the url from Craft for the specific entry
  const { data } = await fetch(
    `
      {
        entry(uid: "${req.query.entryUid}") {
          url
        }
      }
    `
  );

  if (!data?.entry?.url) {
    return res.status(404).json({
      message: `URL of the entry "${req.query.entryUid}" could not be fetched`
    });
  }
  
  // (3)
  // Set the token as preview data
  res.setPreviewData({
    previewToken: req.query.token ?? null
  });

  // (4)
  const parsedUrl = new URL(data.entry.url);

  // Redirect to the path from the fetched url
  res.writeHead(307, { Location: parsedUrl.pathname });
  res.end();
};
```

This function does the following:

1. The function expects minimum 2 query params in the URL, "x-craft-live-preview" which gets set by Craft automatically in preview mode and "entryUid" so that we have the uid of the currently previewed entry. If they are not present, we will stop the function.

2. We try to fetch the URL of the entry from Craft so that we can redirect to the right page.

3. If the query params have a token param, we will set it as preview data.

4. We use the URL of the Entry and redirect to the path (Next.js doesn't like the domain part of the URL)

# Configuration in Craft

You have to configure your section (in my case the Homepage section) with a preview URL which points to the previously built preview API endpoint in Next.js (Next.js frontend is running under localhost:3000): `http://localhost:3000/api/preview?entryUid={sourceUid}`
It is important to use `{sourceUid}` and not `{uid}` because the uid could be different in draft entries, but they are not queryable.

**Et voilà: A static generated homepage, which can show a live preview in Craft whenever you edit the content.**

# Todos

- localhost:8888 for the Craft CMS and localhost:3000 for Next.js are hardcoded. This should be configured with .env files.