How to use Next.js Preview Mode with the Cosmic headless CMS
Tags: nextjs, jamstack, headlesscms, webdev
Published: 2022-08-11 16:06:29

Static web page generation is practical when fetching data from a [headless CMS](https://www.cosmicjs.com/headless-cms). However, when adding new content to our CMS after our site is built, we will want a way to preview draft content that is not yet ready to be published.

Using [Next.js Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode), we solve this issue by rendering pages at request time, allowing us to see unpublished content that was not present during static generation. Let’s explore how we can use Next.js Preview Mode with a headless CMS to preview draft content for ourselves and collaborators.

![Using Preview Mode in the Cosmic Dashboard](https://imgix.cosmicjs.com/5b60ff90-182a-11ed-929a-41fce62497e0-preview-mode.gif)

In this example, we’re using [Cosmic](https://www.cosmicjs.com/) as our headless CMS. You can check out the [live demo](https://nextjs-developer-portfolio-cms.vercel.app/) and [clone the Developer Portfolio template GitHub repository](https://github.com/cosmicjs/nextjs-developer-portfolio/).

## Setting our secret preview token and URL

To ensure only those with access can see our preview URLs, let’s create a string for our secret preview token. You can use this [API key generator](https://generate-random.org/api-key-generator/64-bit/mixed-numbers) to get a base64 secret string, or just use a simple text string that you create.

Now we set the custom preview URL, which will be located at ‘pages/api/preview.js’ in our Next.js project.

```html
  https://<YOUR-SITE>/api/preview?secret=<YOUR_SECRET_PREVIEW_TOKEN>&slug=[object_slug]
```

Setting this preview URL in Cosmic is easy. We can navigate to the settings of an Object Type, and set the **Preview Link** using the convention above. For testing during development, set <YOUR_SITE> to localhost, otherwise set it to your desired URL. Using Cosmic, set the **slug** parameter to **[object_slug]**. Cosmic will automatically convert this short code to the slug of a given Object.

![Setting the preview link in the Cosmic dashboard](https://imgix.cosmicjs.com/28932b60-182a-11ed-929a-41fce62497e0-setting-preview-link.gif)

## Getting the preview post by slug

First, let’s create an async function to fetch the preview post data from our headless CMS using the [Cosmic NPM module](https://www.npmjs.com/package/cosmicjs).

```jsx
const Cosmic = require('cosmicjs')
const api = Cosmic()

const bucket = api.bucket({
  slug: YOUR_BUCKET_SLUG,
  read_key: YOUR_READ_KEY,
})
const is404 = error => /not found/i.test(error.message)

export async function getPreviewPostBySlug(slug) {
  const params = {
    query: { slug },
    status: 'any',
    props: 'slug',
  }

  try {
    const data = await bucket.getObjects(params)
    return data.objects[0]
  } catch (error) {
    // Don't throw if a slug doesn't exist
    if (is404(error)) return
    throw error
  }
}
```

By setting the **status** param to **any,** we fetch all unpublished Objects with a **draft** **status** as well as the latest **draft** of a **published** Object upon request.

## Creating the preview API route

Now that we’ve configured the secret preview token in our CMS, we create an API route that checks that the token matches and then fetches the unpublished post we requested with the preview URL we set earlier (if it exists).

Once the token matches and if the slug we requested exists in our CMS, we enable Preview Mode and set the cookies with `res.setPreviewData({})` using a [Temporary Redirect](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) to the location of our unpublished post.

```jsx
export default async function preview(req, res) {
  // Check the secret and next parameters
  // This secret should only be known to this API route and the CMS
  if (
    req.query.secret !== process.env.COSMIC_PREVIEW_SECRET ||
    !req.query.slug
  ) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  // Fetch the headless CMS to check if the provided `slug` exists
  const post = await getPreviewPostBySlug(req.query.slug)

  // If the slug doesn't exist prevent preview mode from being enabled
  if (!post) {
    return res.status(401).json({ message: 'Object not found' })
  }

  // Enable Preview Mode by setting the cookies
  res.setPreviewData({})

  // Redirect to the path from the fetched post
  // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
  res.writeHead(307, { Location: `/posts/${post.slug}` })
  res.end()
}
```

## Updating getStaticProps

Now that we have set the Preview Mode cookies with `res.setPreviewData`, [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching/get-static-props) will be called upon request.

On the page where we render our preview post, `getStaticProps` will accept an object as an argument, which will be the unpublished data we fetched from our headless CMS. 

```jsx
export async function getStaticProps({ params, preview = null }) {
  const data = await getPostAndMorePosts(params.slug, preview)
  return {
    props: {
      preview,
      post: {
        ...data.post,
      },
    },
  }
}
```

## Using Preview Mode in Cosmic

Now that we’ve set up our API routes and functionality for preview mode, using it with Cosmic is as easy as clicking a button. Cosmic will take the preview URL we set up earlier and generate a query for each Object we create, automatically adding the Object slug to the end of it.

![Using Preview Mode from the Cosmic dashboard](https://imgix.cosmicjs.com/26046a30-182a-11ed-929a-41fce62497e0-use-preview-mode-cosmic.gif)

## Exiting Preview Mode

We can create one more API route to exit Preview Mode. We are simply going to clear the preview cookies, and redirect to the home page.

```jsx
export default async function exit(_, res) {
  // Exit the current user from "Preview Mode". This function accepts no args.
  res.clearPreviewData()

  // Redirect the user back to the index page.
  res.writeHead(307, { Location: '/' })
  res.end()
}
```

Since we are setting the cookies for the current session of our application, we can treat this data as a context for the entire app. In the [live demo](https://nextjs-developer-portfolio-cms.vercel.app/) used in this tutorial, I’ve created a banner that displays when the application is in Preview Mode. In the banner, we then use a [Next.js Link](https://nextjs.org/docs/api-reference/next/link) to route to the **exit-preview** API route, clearing the preview mode cookies and bringing the application back to the home page.

![Exiting Preview Mode](https://imgix.cosmicjs.com/27df1da0-182a-11ed-929a-41fce62497e0-exit-preview-mode.gif)

Whether you are wanting to share new content with your team, or simply want to see your own content before it goes live, using Next.js Preview Mode is a solid solution for doing so. Sharing content is as easy as providing the preview URL with your team, or clicking a button in Cosmic.

I hope you found this article useful. You can tune into our new show [Build Time](https://www.cosmicjs.com/blog/introducing-build-time-a-new-podcast-by-cosmic), where we cover everything from headless CMS, Next.js, React, and much more.