How to Set Up Content Preview in Next.js with Strapi 5 Draft & Publish
Learn step‑by‑step how to enable Strapi 5 Draft & Publish, expose previewable content through the Document Service API, and configure Next.js preview mode for secure live previews.
By the Revisionary team · · 4 min read
Overview
Strapi 5’s Draft & Publish feature lets editors work on unpublished content while keeping the live site stable. By exposing draft data through the Document Service API you can power a preview mode in a Next.js front end. The result is a fast, secure way for marketers and editors to see exactly what a published page will look like before it goes live.
The short answer: enable Draft & Publish on the relevant content types, create a protected preview endpoint in Strapi that returns the draft document, configure Next.js preview and previewData handlers, and protect the endpoint with a short‑lived token.
Below is a complete, production‑ready guide that covers every step, from Strapi configuration to Next.js integration, security considerations, and testing.
1. Enable Draft & Publish in Strapi
- Open the Strapi admin panel.
- Navigate to Content‑type Builder → select the collection type you want previewable (e.g., Article).
- In the Settings tab, toggle Draft & Publish on and click Save.
- Strapi will add two hidden fields to the model:
publishedAt(timestamp) anddraft(boolean). Existing entries are automatically marked as published.
2. Expose Draft Content via the Document Service API
The Document Service API (DSA) is the recommended way to query a single entry with all its relational data. By default it returns only published documents, but you can request drafts by adding the publicationState query parameter.
GET /api/articles/:id?publicationState=preview
Accept: application/jsonpublicationState=live– default, returns only published content.publicationState=preview– returns the latest draft, even if not published.
2.1 Create a Preview Route
Create a custom route that validates a preview token and forwards the request to the DSA.
/src/api/preview/routes/custom.js
module.exports = {
routes: [
{
method: 'GET',
path: '/preview/:model/:id',
handler: 'preview.get',
config: {
policies: ['api::preview.preview-token'], // custom policy
},
},
],
};/src/api/preview/controllers/preview.js
module.exports = {
async get(ctx) {
const { model, id } = ctx.params;
// Forward request to the built‑in Document Service API
const response = await strapi.service('api::document.document').findOne(model, id, {
publicationState: 'preview',
});
ctx.body = response;
},
};2.2 Implement the Token Policy
Store a secret in config/plugins.ts or an environment variable (PREVIEW_TOKEN). The policy checks the query string ?token=….
/src/policies/preview-token.js
module.exports = async (ctx, next) => {
const token = ctx.query.token;
if (!token || token !== process.env.PREVIEW_TOKEN) {
return ctx.unauthorized('Invalid preview token');
}
await next();
};Add the policy to config/policies.js if your project uses the classic config style.
3. Set Up Next.js Preview Mode
Next.js provides two special API routes: pages/api/preview.js and pages/api/exit-preview.js. They toggle a cookie (next-preview) that makes getStaticProps run in preview mode.
3.1 Create the Preview API Route
// pages/api/preview.js
export default async function handler(req, res) {
const { slug, token } = req.query;
if (!token) {
return res.status(401).json({ message: 'Missing token' });
}
// Verify token by calling Strapi preview endpoint
const previewRes = await fetch(
`${process.env.STRAPI_URL}/preview/article/${slug}?token=${token}`
);
if (!previewRes.ok) {
return res.status(401).json({ message: 'Invalid token or draft not found' });
}
// Enable Next.js preview mode
res.setPreviewData({});
// Redirect to the page that will render the draft
res.writeHead(307, { Location: `/${slug}` });
res.end();
}3.2 Create the Exit‑Preview Route
// pages/api/exit-preview.js
export default function handler(req, res) {
res.clearPreviewData();
res.writeHead(307, { Location: '/' });
res.end();
}3.3 Adjust getStaticProps to Honor Preview
// pages/[slug].js
export async function getStaticProps({ params, preview }) {
const slug = params.slug;
const endpoint = preview
? `${process.env.STRAPI_URL}/preview/article/${slug}?token=${process.env.PREVIEW_TOKEN}`
: `${process.env.STRAPI_URL}/api/articles/${slug}`;
const res = await fetch(endpoint);
const data = await res.json();
return {
props: { article: data },
// Re‑validate every 60 seconds in production
revalidate: preview ? false : 60,
};
}When preview is true, Next.js calls the Strapi preview route, which returns the draft version.
4. Secure the Preview Flow
- Short‑lived Token – Generate a random token (e.g., UUID) for each preview request from the CMS UI. Store it in a temporary DB table with a TTL of 5‑10 minutes.
- HTTPS Only – Serve both Strapi and Next.js over TLS to protect the token in transit.
- IP Restriction (optional) – Add a policy that limits preview requests to known editorial IP ranges.
- Rate Limiting – Use a reverse‑proxy (NGINX, Cloudflare) to limit requests to
/preview/*.
The combination of a one‑time token and TLS ensures that only authorized editors can view unpublished content.
5. Testing the Preview Flow
- In the Strapi admin, edit an Article and Save without publishing.
- Click the Preview button (you may need to add a custom UI action that opens
/api/preview?slug=…&token=…). - The browser should be redirected to the article page on the Next.js site, showing the draft content.
- Verify that the
next-previewcookie is set (document.cookie). - Open a private/incognito window – you should see the published version, proving the preview is cookie‑scoped.
- Call
/api/exit-previewand ensure the cookie is cleared and the live version appears again.
6. Common Pitfalls & How to Fix Them
| Symptom | Cause | Fix |
|---|---|---|
| 404 from Strapi preview endpoint | Wrong model or id in URL | Use the exact collection type name (lower‑case, plural) and ensure the slug matches the entry ID. |
| Preview shows stale data | Cache on CDN or Vercel | Set Cache-Control: no‑store on the preview API response or add revalidate: false in getStaticProps. |
| Token always invalid | Token not synced between Strapi and Next.js env | Store the token in an environment variable on both sides, or implement a runtime token exchange endpoint. |
| Draft fields missing | Query does not include publicationState=preview | Verify the custom preview route forwards publicationState: 'preview' to the Document Service API. |
| Preview works locally but not in production | CORS or missing env vars | Add Strapi CORS config to allow the Next.js domain, and double‑check STRAPI_URL and PREVIEW_TOKEN are set in the production environment. |
7. Performance Considerations
- Selective Fields – Use the
fields[]query parameter to request only the fields needed for rendering, reducing payload size. - Cache Drafts Sparingly – Draft content changes frequently; avoid long‑term caching. A short in‑memory cache (e.g., 30 seconds) can reduce load during rapid edits.
- Batch Requests – If a page needs several related entries, use the Document Service API’s
populateoption to fetch them in a single request.
8. Extending the Workflow with Revisionary (optional)
If you have the Revisionary plugin, you gain extra safety:
- The Version history panel lets editors compare the draft with the last published version before triggering a preview.
- The Review space can be used to require editorial approval before a preview token is generated.
- All preview actions are recorded in the Audit log, providing traceability for compliance.
Integrating Revisionary is optional but recommended for teams that need rigorous change control.
Conclusion
Setting up a reliable preview environment with Strapi 5 and Next.js involves three core steps: enable Draft & Publish, expose drafts through a secure preview endpoint, and configure Next.js preview mode. By following the code snippets and security best practices above, you give content editors immediate visual feedback without risking accidental publication. The same pattern works for any collection type, and can be extended with Revisionary for full version‑control and audit capabilities.
Now you can confidently let marketers and editors preview pages, blog posts, or product listings before they go live, improving both content quality and release velocity.
The Revisionary team
Developers who build Revisionary, a version history plugin for Strapi 5. We write about Strapi, web development, AI and application security.
This article was written with the help of AI.