How to enable Preview Mode on an app powered by Next.js & Contentful
Tags: javascript, contentful, nextjs, ssg
Published: 2020-05-22 04:00:29

## What? 

**Fire up those IDEs:** This article will walk you through how to enable Preview Mode on a Next.js powered website or app that's content is managed with Contentful.


<hr>

## Why? 

You're probably here because you built your company a really dope Next.js powered app that manages it's content with [Contentful](https://www.contentful.com/faq/about-contentful/#what-is-contentful). 

Great work! But, now those folks who create your engaging, captivating, money-making content want a way to preview what their drafted work would look like when it's live **before** they publish it for all the world to see. 

Great news! You're in luck because you chose to build with two tools that both want you to preview your work -- good lookin' out, friends. :tada:

<hr>

## How? 

Of course, the configuration will vary based on all the intricacies of how you built your unique app, but this oughta give you a high-level of how Next.js & Contentful's preview features work together. So, let's take a look at a basic example on a blog site!

**At a high-level, this is how Preview Mode is functioning on this example blog:** 

- All of the blog’s pages are statically generated and we’re querying [Contentful's Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/) for the blog data at build time.
- In Contentful, you can create a new unpublished blog post or make unpublished edits to an existing blog post.

![https://cdn.zappy.app/8e4d459b8d2f1283fc416cb8efcfe621.png](https://cdn.zappy.app/8e4d459b8d2f1283fc416cb8efcfe621.png)

- If you want to preview the changes, click on the "Open Preview" button.

![https://cdn.zappy.app/dc4976e38b1db9868dd87bc115c6c1e8.png](https://cdn.zappy.app/dc4976e38b1db9868dd87bc115c6c1e8.png)

- This will open a new tab in the browser, hit this blog's `api/preview` route (that we will build below) that will trigger two Next.js preview mode cookies to be added to the browser, then will redirect the user back to the URL of the blog post they intend to preview.
- Any requests to Next.js containing these cookies will be seen as preview mode, and the pages that used to be statically generated (SSG) will now be served with server-side rendering (SSR). We are then able to query [Contentful's Content Preview API](https://www.contentful.com/developers/docs/references/content-preview-api/) rather than the [Content Delivery API](https://www.contentful.com/developers/docs/references/content-delivery-api/), which will pick up all those unpublished changes.
- When the user is done previewing, you can build a handy dandy button in something like a Preview Mode banner so they can click to exit Preview Mode (which really just sends them through the blog's `api/preview-disable` route, removing those two Next.js Preview Mode cookies) and redirect them back to the blog's non-Preview Mode page.
![Preview Mode Banner](https://cdn.zappy.app/4a4cf133e4f05a5f52bbc0d3cb3fa940.png)

<hr>

## Let's dig into the details! 

*Wanna know more about how exactly this was implemented? Read on...* 

### Set up Preview URL in Contentful

- *[Contentful's Preview URL Documentation](https://www.contentful.com/help/setup-content-preview/)*

- Inside `Contentful > Settings > Content preview`, set a preview URL on the content type that you want to preview. 
- That preview URL needs to pass through the preview API in your Next.js app (`/blog` in my case here) so that when that URL is visited, it will trigger preview cookies to be set that will tell Next.js to enable preview mode.
- We then pass it the path to the content we want to preview (`blog/{slug}` for example).

![Preview URL in Contentful](https://cdn.zappy.app/df2eaa964fdd1496240f61bd85550ffc.png)

### Add the Preview Access Token to your code

- The content preview access token for your Contentful Space will return every single thing that the content delivery access token will **+ DRAFTED CONTENT**

![Preview Access Token in Contentful](https://cdn.zappy.app/8ecf35b292be0ae7f085192169db859d.png)

- Inside the app's `next.config.js`, we need to build the URL that will fetch data using the content preview access token like this:

```jsx
module.exports = {
  env: {
    contentfulGraphQLUrl: `https://graphql.contentful.com/content/v1/spaces/${spaceId}?access_token=${accessToken}`,
    contentfulPreviewGraphQLUrl: `https://graphql.contentful.com/content/v1/spaces/${spaceId}?access_token=${previewAccessToken}`,
  },
};
```

### Set up Preview Mode in Next.js

- *[Next.js Preview Mode documentation](https://nextjs.org/docs/advanced-features/preview-mode)*
- **Create API route that will turn preview mode on**
    - In the aforementioned documentation, it will show you how to create your preview API route. Inside this `api/preview` file, you will call `setPreviewData` on the response object.
    - This will set two preview cookies in the browser that will essentially "turn preview mode on". Any requests to Next.js containing these cookies will be seen as preview mode, and the pages that used to be statically generated (SSG) will now be served with server-side rendering (SSR)
    - You can then route the user to the actual blog post they wanted once the `setPreviewData` has added those cookies and query Contentful's Content Preview API (rather than the Content Delivery API) and we will then be fetched the unpublished (draft) content so that we can preview it!

```jsx
// api/preview.tsx

import { NextApiRequest, NextApiResponse } from 'next';

/**
 * Perform a server side redirect
 * https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
 * https://nodejs.org/api/http.html#http_class_http_serverresponse
 */
function serverSideRedirect(
  res: ServerResponse,
  destinationPath: string,
  statusCode = 301,
) {
  res.writeHead(statusCode, { Location: destinationPath });
}

export default (req: NextApiRequest, res: NextApiResponse) => {
  // Calling setPreviewData sets a preview cookies that turn on the preview mode.
  // Any requests to Next.js containing these cookies will be seen as preview mode,
  // and the behavior for statically generated pages will change.
  res.setPreviewData({
    maxAge: 60 * 60, // The preview mode cookies expire in 1 hour
  });
  const { slug } = req.query;
  serverSideRedirect(res, paths.blogArticle(slug as string), 307);
  res.end();
};
```
- **Create API route that will disable preview mode**
    - Just like you created the `preview` route, you'll create a `preview-disable` route.
    - _Additionally: In the `api/preview` file, when `setPreviewData` is called, you can set a `maxAge` so that the preview mode will expire after one hour as a means of disabling the preview mode. See code above._

    - I might also suggest building a Preview Mode banner to allow your content folks the ability to manually disable preview mode. When they click this button, they should be sent through the `api/preview-disable` route which removes the two Next.js Preview Mode cookies and redirects them back to the blog post's URL so that they will land on the published version of the post -- or hopefully a 404 if the content has never been published.

```jsx

// api/preview-disable.tsx

import { NextApiRequest, NextApiResponse } from 'next';

/**
 * Perform a server side redirect
 * https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering
 * https://nodejs.org/api/http.html#http_class_http_serverresponse
 */
function serverSideRedirect(
  res: ServerResponse,
  destinationPath: string,
  statusCode = 301,
) {
  res.writeHead(statusCode, { Location: destinationPath });
}

export default (req: NextApiRequest, res: NextApiResponse) => {
  // Clears the preview mode cookies.
  // This function accepts no arguments.
  res.clearPreviewData();
  serverSideRedirect(res, req.headers.referer || '/', 307);
  res.end();
};
```

![Preview Mode Banner](https://cdn.zappy.app/4a4cf133e4f05a5f52bbc0d3cb3fa940.png)

### Dynamically choose the appropriate access token

- When you request a page from a Next.js app that has `getStaticProps` with the preview mode cookies set (via `res.setPreviewData`), then `getStaticProps` will be called at **request time** (instead of at build time). WOWZA! Nifty hybrid, Next.js. High-five! 
- Furthermore, it will be called with a `context` object where `context.preview` will be `true`.
- You can then pass that preview boolean over to where your client is being created to specify which client to use. I'm using [`urql`](https://formidable.com/open-source/urql/docs/) (a blazingly fast, lightweight, and customizable GraphQL client) to make a `client` AND a `previewClient` and the value of that preview boolean will determine which client is used when we query Contentful.

```jsx
export const previewClient = createClient({
  url: process.env.contentfulPreviewGraphQLUrl,
});

export const client = createClient({
  url: process.env.contentfulGraphQLUrl,
});

export const getClient = (preview: boolean) =>
  preview ? previewClient : client;
```

### Query Contentful for preview data

- Now wherever in your code you're querying Contentful for that specific content, you'll need to pull that `preview` boolean from the `context` object and pass it along to the query so that Contentful knows whether or not to send drafted content along with the data.

```jsx
const blogArticleQuery = gql`
  query articleQuery($slug: String!, $preview: Boolean!) {
    article: blogArticleCollection(where: { slug: $slug }, preview: $preview) {
      ...
    }
  }
`;
```

- This will mean that if preview mode is off and `preview` is false, the app will use the content delivery access token and URL. But if the `preview` is true, it should use the content preview access token and URL and will send true along with the query to serve that preview/draft data!

<hr> 

## The end! 

There you have it. That's how you give your content team the ability to preview their content! :sparkles: 

I realize this is a very heavy topic for such a short blog post, but I assure you Next.js & Contentful have incredible documentation on these processes. This article is intended to be a friendly little bridge between the two. When you start digging in, with the knowledge you have of your own app, I think you'll find it pretty exciting and fun to build! 

And of course, your Twitter dev community loves to share knowledge. Hit me up with questions and I'll see how I can help ya. :hearts: [@the_real_stacie](https://twitter.com/the_real_stacie)

_Disclaimer: don't try copy/paste with all this. It's totally picked apart to just hand over the basics. You'll want to understand what's happening in your code AND these codes snippets above well enough to know how it can fit into your masterpiece._