rsnext/docs/advanced-features/preview-mode.md
Shu Uesugi bf0ea49b02
DatoCMS Example (#10891)
* Copy cms-datocms

* Update package.json

* Desktop design

* Tablet support

* Mobile styles

* Update titles

* Add article page

* Minor tweaks

* Fix height issue

* Improve text colors

* Extract Alert and Footer components

* Extract avatar

* Add Date

* Extract container

* Fix capitalization

* Make it work with no backend

* Add paragraph styles

* Extract PostPreview

* Extract h2

* Extract more stories

* Refactor into more components

* Update tags

* Add missing key

* Add custom document for lang="" support

* Add dotenv

* Load data for the index page

* Limit posts

* Show post page

* Add markdown processing

* Forgot margin

* Add links

* Remove files and add fallback

* Add og:image

* Add favicon

* Add aria-label

* Fix prerender

* Learn more → read documentation

* Fix links and footer

* Desaturate using imgix

* Add preview secret

* Add preview support

* Fix preview code

* Exit preview mode

* Extract getAllPostsWithSlug

* Extract getAllPostsForHome

* Extract getPreviewPostBySlug

* Extract getPostAndMorePosts

* Extract constants

* Extract markdownToHtml

* Fix markdown styles

* Fix imgix for author

* Add README

* Add links to docs
2020-03-09 11:51:08 +01:00

222 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: Next.js has the preview mode for statically generated pages. You can learn how it works here.
---
# Preview Mode
> This document is for Next.js versions 9.3 and up. If youre using older versions of Next.js, refer to our [previous documentation](https://nextjs.org/docs/tag/v9.2.2/basic-features/pages).
In the [Pages documentation](/docs/basic-features/pages.md) and the [Data Fetching documentation](/docs/basic-features/data-fetching.md), we talked about how to pre-render a page at build time (**Static Generation**) using `getStaticProps` and `getStaticPaths`.
Static Generation is useful when your pages fetch data from a headless CMS. However, its not ideal when youre writing a draft on your headless CMS and want to **preview** the draft immediately on your page. Youd want to Next.js to render these pages at **request time** instead of build time and fetch the draft content instead of the published content. Youd want Next.js to bypass Static Generation only for this specific case.
Next.js has the feature called **Preview Mode** which solves this problem. Heres an instruction on how to use it.
## Step 1. Create and access a preview API route
> Take a look at the [API Routes documentation](/docs/api-routes/introduction.md) first if youre not familiar with Next.js API Routes.
First, create a **preview API route**. It can have any name - e.g. `pages/api/preview.js` (or `.ts` if using TypeScript).
In this API route, you need to call `setPreviewData` on the response object. The argument for `setPreviewData` should be an object, and this can be used by `getStaticProps` (more on this later). For now, well just use `{}`.
```js
export default (req, res) => {
// ...
res.setPreviewData({})
// ...
}
```
`res.setPreviewData` sets some **cookies** on the browser which turns on the preview mode. Any requests to Next.js containing these cookies will be considered as the **preview mode**, and the behavior for statically generated pages will change (more on this later).
You can test this manually by creating an API route like below and accessing it from your browser manually:
```js
// A simple example for testing it manually from your browser.
// If this is located at pages/api/preview.js, then
// open /api/preview from your browser.
export default (req, res) => {
res.setPreviewData({})
res.end('Preview mode enabled')
}
```
If you use your browsers developer tools, youll notice that the `__prerender_bypass` and `__next_preview_data` cookies will be set on this request.
### Securely accessing it from your Headless CMS
In practice, youd want to call this API route _securely_ from your headless CMS. The specific steps will vary depending on which headless CMS youre using, but here are some common steps you could take.
These steps assume that the headless CMS youre using supports setting **custom preview URLs**. If it doesnt, you can still use this method to secure your preview URLs, but youll need to construct and access the preview URL manually.
**First**, you should create a **secret token string** using a token generator of your choice. This secret will only be known by your Next.js app and your headless CMS. This secret prevents people who dont have access to your CMS from accessing preview URLs.
**Second**, if your headless CMS supports setting custom preview URLs, specify the following as the preview URL. (This assumes that your preview API route is located at `pages/api/preview.js`.)
```bash
https://<your-site>/api/preview?secret=<token>&slug=<path>
```
- `<your-site>` should be your deployment domain.
- `<token>` should be replaced with the secret token you generated.
- `<path>` should be the path for the page that you want to preview. If you want to preview `/posts/foo`, then you should use `&slug=/posts/foo`.
Your headless CMS might allow you to include a variable in the preview URL so that `<path>` can be set dynamically based on the CMSs data like so: `&slug=/posts/{entry.fields.slug}`
**Finally**, in the preview API route:
- Check that the secret matches and that the `slug` parameter exists (if not, the request should fail).
-
- Call `res.setPreviewData`.
- Then redirect the browser to the path specified by `slug`. (The following example uses a [307 redirect](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307)).
```js
export default async (req, res) => {
// Check the secret and next parameters
// This secret should only be know to this API route and the CMS
if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
return res.status(401).json({ message: 'Invalid token' })
}
// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS
const post = await getPostBySlug(req.query.slug)
// If the slug doesn't exist prevent preview mode from being enabled
if (!post) {
return res.status(401).json({ message: 'Invalid slug' })
}
// 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: post.slug })
res.end()
}
```
If it succeeds, then the browser will be redirected to the path you want to preview with the preview mode cookies being set.
## Step 2. Update `getStaticProps`
The next step is to update `getStaticProps` to support the preview mode.
If you request a page which has `getStaticProps` with the preview mode cookies set (via `res.setPreviewData`), then `getStaticProps` will be called at **request time** (instead of at build time).
Furthermore, it will be called with a `context` object where:
- `context.preview` will be `true`.
- `context.previewData` will be the same as the argument used for `setPreviewData`.
```js
export async function getStaticProps(context) {
// If you request this page with the preview mode cookies set:
//
// - context.preview will be true
// - context.previewData will be the same as
// the argument used for `setPreviewData`.
}
```
We used `res.setPreviewData({})` in the preview API route, so `context.previewData` will be `{}`. You can use this to pass session information from the preview API route to `getStaticProps` if necessary.
If youre also using `getStaticPaths`, then `context.params` will also be available.
### Fetch preview data
You can update `getStaticProps` to fetch different data based on `context.preview` and/or `context.previewData`.
For example, your headless CMS might have a different API endpoint for draft posts. If so, you can use `context.preview` to modify the API endpoint URL like below:
```js
export async function getStaticProps(context) {
// If context.preview is true, append "/preview" to the API endpoint
// to request draft data instead of published data. This will vary
// based on which headless CMS you're using.
const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
// ...
}
```
Thats it! If you access the preview API route (with `secret` and `slug`) from your headless CMS or manually, you should now be able to see the preview content. And if you update your draft without publishing, you should be able to preview the draft.
```bash
# Set this as the preview URL on your headless CMS or access manually,
# and you should be able to see the preview.
https://<your-site>/api/preview?secret=<token>&slug=<path>
```
## More Examples
Take a look at the following examples to learn more:
- [DatoCMS Example](https://github.com/zeit/next.js/tree/canary/examples/cms-datocms)
## More Details
### Clear the preview mode cookies
By default, no expiration date is set for the preview mode cookies, so the preview mode ends when the browser is closed.
To clear the preview cookies manually, you can create an API route which calls `clearPreviewData` and then access this API route.
```js
export default (req, res) => {
// Clears the preview mode cookies.
// This function accepts no arguments.
res.clearPreviewData()
// ...
}
```
### Specify the preview mode duration
`setPreviewData` takes an optional second parameter which should be an options object. It accepts the following keys:
- `maxAge`: Specifies the number (in seconds) for the preview session to last for.
```js
setPreviewData(data, {
maxAge: 60 * 60, // The preview mode cookies expire in 1 hour
})
```
### `previewData` size limits
You can pass an object to `setPreviewData` and have it be available in `getStaticProps`. However, because the data will be stored in a cookie, theres a size limitation. Currently, preview data is limited to 2KB.
### Works with `getServerSideProps`
The preview mode works on `getServerSideProps` as well. It will also be available on the `context` object containing `preview` and `previewData`.
### Unique per `next build`
The bypass cookie value and private key for encrypting the `previewData` changes when a `next build` is ran, this ensures that the bypass cookie cant be guessed.
## Learn more
The following pages might also be useful.
<div class="card">
<a href="/docs/basic-features/data-fetching.md">
<b>Data Fetching:</b>
<small>Learn more about data fetching in Next.js.</small>
</a>
</div>
<div class="card">
<a href="/docs/api-routes/introduction.md">
<b>API Routes:</b>
<small>Learn more about API routes in Next.js.</small>
</a>
</div>
<div class="card">
<a href="/docs/api-reference/next.config.js/environment-variables.md">
<b>Environment Variables:</b>
<small>Learn more about environment variables in Next.js.</small>
</a>
</div>