In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
Live Preview looks simple in a demo: put a URL in a collection config, open an
iframe, and show the draft. A production implementation has more moving parts.
The CMS must know the correct public URL, the frontend must deliberately enter
draft mode, draft reads must bypass published caches, and neither the preview
URL nor the iframe may weaken tenant or locale isolation.
This guide starts with the implementation used by our multi-site website, then
turns it into a reusable setup for Payload CMS and the Next.js App Router. It
also covers template-definition records, which describe presentation but do
not have a public page of their own.
The examples reflect Payload 3.88.0, @payloadcms/live-preview-react3.88.0, and Next.js 16.3.3. Check the reference links at the end when using
a later version.
1. Our use case
Our application has all of the conditions that make preview URLs more than a
string interpolation exercise:
one Payload installation serves multiple sites;
each site can enable a different set of locales;
English and French use different route segments, such as /en-CA/blog/...
and /fr-CA/blogue/...;
published pages use Next.js Cache Components, while drafts must remain
request-time and uncached;
Blogs, Pages, Recipes, Giveaways, and Products share Live Preview;
a separate template-definitions collection selects approved, code-owned
presentation variants;
editors need both the in-admin iframe and a normal Preview button.
For a Blog document, the flow is:
Diagram
The Blog collection supplies a conventional admin.preview callback:
Both controls use the same routing and security rules, but they are separate:
Feature
Configuration
Editor experience
Preview
admin.preview
Opens the generated URL as a normal page/tab
Live Preview
admin.livePreview or collection/global admin.livePreview
Embeds the URL in Payload's preview panel and sends update events
Configuring one does not automatically configure the other.
2. Why the implementation is split into layers
A robust preview system has five responsibilities:
Payload drafts and versions preserve unpublished state.
The URL builder converts CMS state into the correct public site, locale,
and route.
The draft endpoint authenticates the request and enables Next.js Draft
Mode.
The data loader selects draft or published records without mixing their
cache behavior.
The frontend bridge refreshes a Server Component route after saves or
merges real-time form state in a Client Component.
This separation prevents a common mistake: treating the appearance of a Live
Preview toggle as proof that unpublished content is fetched safely. The Admin
UI is only the entry point; the server query remains the security boundary.
3. Prerequisites
Install the React bridge if needed:
bash
pnpm add @payloadcms/live-preview-react
Its version should normally match Payload. Enable drafts on each collection:
Live Preview works without autosave, but server refresh only shows saved
state. Autosave makes that approach feel live while preserving Server
Component rendering.
Generate a secret with openssl rand -base64 48. Never place
PAYLOAD_SECRET in a query string: it signs Payload authentication data. Use
a separate PREVIEW_SECRET and store production values in a secret manager.
4. Configure a collection
Option A: collection-local Live Preview
Use this when a collection has unique routing logic:
The locale shapes differ: livePreview.url receives a locale object, while
preview receives the locale code as a string. Either callback may return a
URL, null, or a promise. Return null when required context is missing.
Option B: root Live Preview
Use root configuration when many collections share one URL policy:
Prefer a typed lookup or switch over known slugs in application code. Root
configuration adds collections and globals target lists. Collection and
global configs already know their target. A local config can override root
behavior for that entity.
5. Build canonical, secure preview URLs
A single-site tutorial can interpolate /posts/${slug}. A production URL
builder should validate and bind the routing context:
the origin comes from trusted configuration, never a browser Referer;
the path uses the same canonical route registry as the frontend;
the token expires and binds the exact origin and path.
Our token is HMAC-SHA256 signed and includes { origin, path, exp, version }.
Verification uses a timing-safe comparison, rejects unsafe paths, and refuses
a token presented on another origin. JWT is also valid if it enforces the same
claims.
Signing only a shared secret while accepting arbitrary ?slug= input still
permits an open redirect. Redirect to the path recovered from the verified
token or from a verified CMS record.
6. Enable Next.js Draft Mode
The entry endpoint verifies the token, sets Next.js's cookie, and redirects to
the verified public path. Our app registers it as a Payload custom endpoint:
resolveValidatedRequestOrigin must validate forwarded protocol and host
headers for your proxy topology. Payload endpoints sit below its API route, so
with the default /api, path: "/draft" becomes /api/draft.
A Next.js Route Handler at app/api/draft/route.ts is the common alternative.
Use a Payload endpoint for one API surface and Payload request context; use a
Route Handler when CMS and frontend are separate or that matches the existing
architecture. The validation contract is identical. Do not implement both
without a reason.
Cookie-changing operations normally belong to POST. CMS preview links and
iframe sources are navigations, so they commonly need a GET entry. Authenticate
it, keep tokens short-lived, mutate no application data, and send no-store.
Provide an exit endpoint or Server Action using:
ts
const draft = awaitdraftMode();
draft.disable();
Set prefetch={false} on an exit <Link> so prefetching cannot disable the
cookie unexpectedly.
Use overrideAccess: true only for trusted server frontend code that enforces
site, locale, slug, and publication rules independently. Local API work on
behalf of a user should use overrideAccess: false and pass req.
Return a narrow presentation projection, not a raw Payload document. Match
relationship depth to the renderer and handle relationships that are IDs,
missing, or inaccessible.
Our router puts this below <Suspense>, calls await connection() in the
draft branch, and keeps published loaders in dedicated "use cache"
functions. Next.js Draft Mode bypasses the fetch cache, Cache Components,
unstable_cache, and ISR for the request. isEnabled is readable in a cache
scope, but enable() and disable() must run outside one.
Mount it only in Draft Mode. With autosave, Payload saves the version, emits a
document event, and the bridge refreshes the route.
Advantages: Server Components work unchanged; relationship population, hooks,
and transforms come from a real Payload read; preview exercises production
routing. Tradeoffs: updates wait for save/autosave, include server latency,
and require the serverURL to match the Admin event origin.
B. Merge form changes with useLivePreview
Use a Client Component for character-by-character feedback:
Trusted Payload origin whose messages are accepted
depth
No
Relationship population depth
apiRoute
No
Non-default Payload API route
requestHandler
No
Custom population handler for proxying or middleware
The hook returns { data, isLoading }. It gives immediate field changes, but
the preview must be client-side; server-only presentation needs a boundary;
and relationships/uploads still need careful population and ID-only handling.
Posted preview data is never authorization for a server mutation.
C. Hybrid
A hybrid can use the hook for immediate local fields and refresh after saves
for relationships and derived output. It is richer but creates two preview
state sources. Define which source wins after save before adopting it.
9. Previewing Template Definitions
A Template Definition selects a layout; it is not itself a public article.
Preview a real document through the draft definition:
Resolve missing or unknown keys to default. This protects new sites,
incomplete seeds, removed configuration, and preview sessions.
If no representative document exists, deliberately return null, use a
code-owned fixture, or render a dedicated template sandbox. Never borrow a
document from another Site or locale.
10. Configuration reference
These types reflect Payload 3.88.0.
LivePreviewConfig
Property
Type
Meaning
url
URL value or sync/async callback
Iframe source; null/undefined suppresses preview
breakpoints
{ name, label, width, height }[]
Toolbar devices; dimensions accept numbers or strings; responsive is included by default
openByDefault
boolean
Opens preview on first document view; stored user preference later wins; default false
The URL callback receives data, locale, req, optional
collectionConfig, optional globalConfig, and deprecated payload (use
req.payload). It may run often with autosave, so avoid expensive work.
Root admin.livePreview adds collections?: string[] and
globals?: string[].
Use req for authorized reads. Do not copy Payload's auth token into a
public URL; issue a purpose-specific preview token.
Globals
Globals support local admin.livePreview and admin.preview. Because one
global may affect many routes, choose a fixed sandbox, homepage, or
representative page that visibly uses it.
React exports
@payloadcms/live-preview-react exports:
RefreshRouteOnSave, with required refresh and serverURL, plus optional
apiRoute and depth;
useLivePreview, with the options above.
The lower-level @payloadcms/live-preview package exposes subscribe,
unsubscribe, and ready. Prefer the React wrapper unless you need to own the
event lifecycle.
11. Security checklist
Drafts/versions are enabled.
URL callbacks derive Site and locale on the server.
Site reads respect request access.
The locale is enabled and fallbackLocale: false preserves isolation.
Origins come from a trusted allowlist/host map, not Referer.
PAYLOAD_SECRET never appears in a URL.
PREVIEW_SECRET is separate, random, long, and secret-managed.
Tokens expire and bind the exact origin and safe relative path.
Signatures use safe comparison and the endpoint cannot open-redirect.
Proxy host/protocol handling matches the deployment topology.
Redirects send no-store and no-referrer.
Draft loaders cannot cross Site or locale boundaries.
Public loaders explicitly require published visibility.
Client Components receive narrow projections, not raw documents.
CSP, frame, cookie, and proxy policies allow the intended iframe.
In our staging environment, PREVIEW_SECRET is managed in Infisical with the
other runtime secrets.
12. Troubleshooting
No Live Preview control
Register the collection in root collections or add local livePreview.
Configuring only preview creates no iframe control. Restart after config
changes.
No Preview button
Add admin.preview; Live Preview does not create it. A new document may lack
Site, locale, or slug, in which case returning null is correct.
Blank or refused iframe
Inspect the URL/response, Content-Security-Policyframe-ancestors,
X-Frame-Options, cookie policy, public origins, and reverse-proxy headers.
Only published content appears
Confirm draftMode().isEnabled, draft: true, removal of public-only
_status conditions in draft mode, and the existence of a draft version.
Changes do not update
For server refresh, mount the bridge in draft mode, match
NEXT_PUBLIC_SERVER_URL, and confirm save/autosave runs. For the hook, ensure
the component uses returned data, not only initialData.
Wrong Site or language
Scope every query by trusted Site context, use fallbackLocale: false, and
build localized paths from one route registry. Never derive tenant ownership
from a fetched content document.
Stale Cache Components output
Keep preview branching close to draftMode(), do not send draft input through
published cache functions, and do not application-cache draft query results.
Template preview does not change
Query the definition with draft: true; verify the representative document's
Site/locale; ensure the key exists; retain the default registry entry.
13. Test plan
Intended collections expose the iframe and, separately, Preview button.
English and French URLs use their exact localized routes.
Disabled Sites, bad locales, missing slugs, and untrusted origins return
null.
Valid tokens enable Draft Mode and redirect only to their bound path.
Tampered, expired, wrong-origin, malformed, and unsafe-path tokens fail.
Draft queries render unpublished documents; public queries do not.
Site and locale isolation hold in both modes.
Save/autosave events refresh the Server Component route.
Missing/unknown template keys resolve to the code-owned default.
Template previews use only same-Site, same-locale representatives.
Test the workflow in a browser too. Unit tests cannot reveal iframe policy,
cookie partitioning, proxy headers, or responsive-toolbar problems.
14. Decision guide
Situation
Recommended setup
One collection, one route
Local admin.livePreview plus admin.preview
Many collections share routing
Root config with typed collection-to-route mapping
Mostly Server Components
RefreshRouteOnSave and autosave
Immediate typing feedback
useLivePreview Client Component
Relationships/server transforms dominate
Server refresh or a carefully defined hybrid
Config record has no public route
Same-context representative or dedicated sandbox
Multi-site/multilingual
Server-resolved origin and canonical route; no fallback
The essential model is: Payload decides where to preview, the entry endpoint
decides whether the request is trusted, Next.js Draft Mode decides which
cache behavior applies, and the data layer decides which version may render.
Treat those as explicit contracts and preview stays predictable as the app
grows.