Static web page generation is practical when fetching data from a 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, 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.
In this example, we’re using Cosmic as our headless CMS. You can check out the live demo and clone the Developer Portfolio template GitHub repository.
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 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.
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 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.
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.
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 to the location of our unpublished post.
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
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.
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.
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.
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 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 to route to the exit-preview API route, clearing the preview mode cookies and bringing the application back to the home page.
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, where we cover everything from headless CMS, Next.js, React, and much more.
Top comments (0)