Draft Preview in a Headless CMS: How Content Preview Actually Works (2026)

Secret-token previews, Next.js Draft Mode, and the security tradeoff

July 27, 2026 · 11 min read
Draft Preview in a Headless CMS: How Content Preview Actually Works (2026)

You edit a blog post, hit save, and want to see it the way readers will—rendered by your Next.js front-end, not the CMS admin. But the post isn't published yet. Your public API only returns published content. So the front-end fetches nothing, and you're stuck guessing how the page looks from a raw markdown editor. That gap is the whole problem with draft preview in a headless setup.

In a traditional CMS like WordPress, preview is free. The CMS renders the page, so it can render a draft too. Decouple the front-end and that assumption breaks. Your React app has no idea a draft exists unless you build a path for it.

TL;DR: How does draft preview work in a headless CMS?

Draft preview means viewing unpublished content in your real front-end before it goes live. In a headless CMS this is harder because the front-end and the content API are separate—the public API hides drafts on purpose, so you need a second, authenticated path to fetch them.

Three common patterns solve it:

  1. Preview API + secret token — a special URL with a secret that unlocks drafts for one request.
  2. Framework preview mode (like Next.js Draft Mode) — a cookie flips your app into "show drafts" mode, then it fetches unpublished content.
  3. On-demand authenticated fetch — your front-end sends an auth token (like a Sanctum bearer token) to an admin endpoint that returns the draft.

The catch is security: a draft endpoint leaks unpublished content if the secret leaks. UnfoldCMS today handles preview through an authenticated admin API—drafts are visible to a logged-in admin via /api/v1/admin/posts/{id}, not through anonymous preview tokens. Token-gated public preview is on the roadmap, not shipped. More on that below.

Why is preview harder in a headless CMS?

Because the front-end and the content are two separate systems. In a coupled CMS, one app both stores and renders content, so it can render a draft. In a headless setup, the CMS only serves data over an API, and that API hides drafts by default to protect them. Your front-end has to ask for drafts on purpose.

Think about what the public API does. When your Next.js site calls GET /api/v1/posts, it gets published posts only. That's correct behavior—you don't want an unpublished draft leaking to a random visitor who guesses a URL. But it means your normal fetch path physically cannot see the draft you're editing.

So preview needs a second path. One that:

  • Knows the draft exists (queries unpublished rows too)
  • Proves the requester is allowed to see it (auth of some kind)
  • Renders through the same front-end templates as production, so the preview is accurate

Most preview bugs come from skipping step three. Teams build a preview that renders in a stripped-down viewer, then ship, and the live page looks different. A real preview runs the actual front-end code.

What is the preview API + secret token pattern?

It's a preview URL that carries a secret. The front-end has a route like /api/preview?secret=XYZ&slug=my-post. If the secret matches, the app enables preview and redirects to the post, fetching the draft instead of the published version. The secret is the gate.

This is the pattern most hosted headless CMSes use. The flow looks like this:

  • The CMS admin has a "Preview" button next to each draft.
  • That button points at your front-end's preview route with a shared secret in the query string.
  • Your front-end checks the secret against an env var (PREVIEW_SECRET).
  • If it matches, the app sets a cookie or session flag and fetches the draft by slug or ID.

The secret is shared between the CMS and the front-end. It never changes per request, which is the weak spot—if it lands in a log, a Slack message, or a browser history someone shares, anyone can view drafts. Some tools rotate the secret or sign a short-lived token instead, which is safer but more work to wire up.

The upside: it's stateless on the CMS side. The CMS just builds a URL. All the preview logic lives in the front-end, where your templates already are.

How does Next.js Draft Mode work?

Next.js Draft Mode is a built-in cookie switch. You call draftMode().enable() inside a route handler, and Next.js sets a signed cookie. While that cookie is present, your data-fetching code can check draftMode().isEnabled and fetch drafts instead of published content. Disabling clears the cookie.

Here's the shape of a preview route in the Next.js App Router:

// app/api/preview/route.js
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request) {
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')

  if (secret !== process.env.PREVIEW_SECRET) {
    return new Response('Invalid token', { status: 401 })
  }

  draftMode().enable()
  redirect(`/blog/${slug}`)
}

Then in your page's fetch, you branch on the mode:

import { draftMode } from 'next/headers'

async function getPost(slug) {
  const { isEnabled } = draftMode()
  const url = isEnabled
    ? `${API}/api/v1/admin/posts?slug=${slug}` // draft path (needs auth)
    : `${API}/api/v1/posts?slug=${slug}`       // published path
  const res = await fetch(url, {
    headers: isEnabled ? { Authorization: `Bearer ${process.env.CMS_ADMIN_TOKEN}` } : {},
    cache: 'no-store',
  })
  return res.json()
}

Two things matter here. Draft Mode turns off static caching for that request (cache: 'no-store'), so you always see the latest edit. And the draft fetch still needs its own auth to the CMS—Draft Mode only flips the front-end switch, it doesn't grant access to your CMS's protected data. That auth is the part people forget.

What's the on-demand authenticated fetch pattern?

It skips the secret-URL dance and just sends an auth token straight to a protected draft endpoint. Your front-end (or a server component) calls the CMS admin API with a bearer token. The CMS checks the token, confirms admin rights, and returns the draft. No shared preview secret needed.

This is closer to how UnfoldCMS works today. UnfoldCMS exposes a REST API at /api/v1/* and uses Sanctum tokens for auth. The public routes return published content. The admin write API and admin read routes—like /api/v1/admin/posts/{id}—require a valid admin token and return drafts too.

So a preview flow on UnfoldCMS looks like:

  • An admin is logged in (or holds an admin Sanctum token).
  • The front-end's preview route calls /api/v1/admin/posts/{id} with Authorization: Bearer <admin-token>.
  • The CMS returns the unpublished post.
  • The front-end renders it through the same templates as a live post.

The tradeoff: the requester must be a real admin. There's no anonymous share link where you send a URL to a client and they see the draft without logging in. That's what token-gated public preview would add—and it's on the UnfoldCMS roadmap, not shipped. Be honest with your team about that if you're comparing tools.

Which preview approach should you use?

Pick based on who needs to see the draft and how much you trust the network. If only admins preview, an authenticated fetch is simplest and safest. If a non-logged-in reviewer (a client, an editor without an account) needs a share link, you need a secret-token or short-lived-signed-token approach—and you accept the leak risk.

Here's how the three stack up:

Approach How it gates access Anonymous reviewer? Main risk Where UnfoldCMS is
Preview API + secret token Shared secret in URL Yes Secret leaks = drafts exposed Not shipped
Framework Draft Mode (Next.js) Signed cookie + your own draft auth Depends on the auth behind it Cookie/token handling in front-end Works if you supply admin auth
On-demand authenticated fetch Bearer token, admin-only No (must be admin) Token stored in front-end env Shipped today

A common real-world setup mixes them: Next.js Draft Mode for the front-end switch, plus an authenticated fetch behind it for the actual draft data. Draft Mode handles the cookie and caching; the bearer token handles CMS access. That's the pattern that matches UnfoldCMS's current API surface.

What are the security concerns with draft preview?

The core risk is leaking unpublished content. Every preview path is a hole in the "published only" wall. If the gate is a static secret in a URL, that secret can end up in server logs, analytics, browser history, or a shared link—and then anyone can read your drafts, including embargoed announcements or unreleased pricing.

Concrete things that go wrong:

  • Secret in the query string gets logged by your web server and CDN by default. Query params are not private. Prefer a signed, short-lived token, or move the secret to a header.
  • Preview endpoint not rate-limited lets someone brute-force post IDs. If /preview?id=123 returns a draft, an attacker can walk the ID space.
  • Draft URLs get indexed if your preview route doesn't send X-Robots-Tag: noindex. Google can crawl a leaked preview link and cache the unpublished page.
  • Admin token baked into a client bundle. If your bearer token ends up in front-end JavaScript that ships to the browser, it's public. Keep draft fetches server-side (server components, route handlers, or a backend-for-frontend), never in client code.

UnfoldCMS's admin-authenticated model sidesteps the first two risks—there's no anonymous preview URL to leak, and the admin API sits behind Sanctum auth. The tradeoff, again, is you can't hand a draft link to someone without an account. When token-gated preview ships, the same log-hygiene and noindex rules will apply, so it's worth learning them now.

How to set up draft preview with UnfoldCMS today

Use the admin API and keep the token server-side. UnfoldCMS gives you a REST API, Sanctum tokens, and an admin read/write API. That's enough to build an accurate, admin-only preview in Next.js or any front-end. You don't get anonymous share links yet, but you get a correct preview for logged-in editors.

Steps:

  1. Create an admin Sanctum token in your UnfoldCMS admin. Treat it like a password.
  2. Store it as a server-only env var (CMS_ADMIN_TOKEN)—never NEXT_PUBLIC_*, never in client code.
  3. Add a preview route in your front-end that enables Next.js Draft Mode and redirects to the post.
  4. Branch your data fetch: published posts from /api/v1/posts, drafts from /api/v1/admin/posts/{id} with the bearer token.
  5. Set cache: 'no-store' on the draft fetch so edits show immediately.
  6. Add noindex to any preview response so search engines don't cache drafts.
  7. Scope the token to the smallest set of permissions you can, so a leak does less damage.

For the scheduled-publish side, UnfoldCMS also supports scheduled publishing and webhooks—so once a draft is approved, you can set a publish time and fire a webhook to trigger a front-end rebuild. Preview handles "does it look right," webhooks handle "it's live now, rebuild the static pages."

FAQ

Does UnfoldCMS have anonymous draft preview links? No. Today preview is admin-authenticated—you fetch drafts through /api/v1/admin/posts/{id} with a Sanctum admin token. Anonymous, token-gated public preview links are on the roadmap, not shipped. If you need to share a draft with someone who has no account, that pattern isn't available yet.

Can I preview drafts in Next.js with UnfoldCMS? Yes. Use Next.js Draft Mode for the front-end cookie switch, then fetch the draft from the admin API with a server-side bearer token. Keep the token out of client-side code. This gives an accurate preview through your real templates.

Why doesn't the public API return drafts? On purpose—so unpublished content can't leak to anonymous visitors who guess a URL. The public /api/v1/* routes return published content only. Drafts live behind the authenticated admin API.

Is a secret preview token safe? It's safe enough for many teams but has real risks. A static secret in a URL can leak through logs, history, or shared links. If you use one, prefer a signed short-lived token, send it in a header not a query string, rate-limit the endpoint, and set noindex.

What's the difference between preview and scheduled publishing? Preview lets you see a draft before it's live. Scheduled publishing sets a future time for a post to go public automatically. UnfoldCMS supports scheduled publishing plus webhooks to rebuild your front-end when a post goes live.

Where UnfoldCMS lands on this

If your preview needs are admin-only, UnfoldCMS covers them now with the REST API, Sanctum tokens, and the admin read API. If you need anonymous share links for non-account reviewers, that's the roadmap item to watch. Want to see the API surface for yourself? Start with what a headless CMS is and the feature list, then wire up an authenticated preview fetch and test it against a real draft.


Sources: Next.js Draft Mode documentation, the UnfoldCMS /api/v1/* REST API and Sanctum auth surface. UnfoldCMS capability claims reflect what's shipped as of July 2026; token-gated anonymous preview is noted as roadmap, not current.

Free & Open Source

Own your CMS. No subscriptions.

Unfold CMS is free to download and self-host. Built on Laravel + React, full source code included.

Share this post:

Discussion

Comments (0)

Leave a Comment

Please log in to leave a comment.

Don't have an account? Register here

No comments yet. Be the first to share your thoughts!

Keep Reading

Related Posts

Back to all posts