Skip to content

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

black and gray laptop displaying codes
Photo by Nate Grant on Unsplash

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.


text
Photo by Mediamodifier on Unsplash

1. Enable Draft & Publish in Strapi

  1. Open the Strapi admin panel.
  2. Navigate to Content‑type Builder → select the collection type you want previewable (e.g., Article).
  3. In the Settings tab, toggle Draft & Publish on and click Save.
  4. Strapi will add two hidden fields to the model: publishedAt (timestamp) and draft (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.

http
GET /api/articles/:id?publicationState=preview
Accept: application/json
  • publicationState=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

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

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

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

js
// 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

js
// 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

js
// 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

  1. 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.
  2. HTTPS Only – Serve both Strapi and Next.js over TLS to protect the token in transit.
  3. IP Restriction (optional) – Add a policy that limits preview requests to known editorial IP ranges.
  4. 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

  1. In the Strapi admin, edit an Article and Save without publishing.
  2. Click the Preview button (you may need to add a custom UI action that opens /api/preview?slug=…&token=…).
  3. The browser should be redirected to the article page on the Next.js site, showing the draft content.
  4. Verify that the next-preview cookie is set (document.cookie).
  5. Open a private/incognito window – you should see the published version, proving the preview is cookie‑scoped.
  6. Call /api/exit-preview and ensure the cookie is cleared and the live version appears again.

6. Common Pitfalls & How to Fix Them

SymptomCauseFix
404 from Strapi preview endpointWrong model or id in URLUse the exact collection type name (lower‑case, plural) and ensure the slug matches the entry ID.
Preview shows stale dataCache on CDN or VercelSet Cache-Control: no‑store on the preview API response or add revalidate: false in getStaticProps.
Token always invalidToken not synced between Strapi and Next.js envStore the token in an environment variable on both sides, or implement a runtime token exchange endpoint.
Draft fields missingQuery does not include publicationState=previewVerify the custom preview route forwards publicationState: 'preview' to the Document Service API.
Preview works locally but not in productionCORS or missing env varsAdd 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 populate option 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.


graphs of performance analytics on a laptop screen
Photo by Luke Chesser on Unsplash

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.

TagsStrapi 5Next.jsDraft and PublishContent previewDocument Service APIHeadless CMS

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.

All articles