Live preview
Server and client modes, how preview works, and local builds.
On this page
Live Preview places the page beside its Admin editor.
Systhema wires up Payload Live Preview (opens in new tab) for Pages, Components, and Posts (when the Posts module is enabled) out of the box — editing a document shows the rendered page in a resizable iframe inside the admin. There are two rendering modes, chosen with the livePreview plugin option:
withSysthema(payloadConfig, {
livePreview: {
mode: 'server', // 'server' (default) | 'client'
depth: 2, // relationship-population depth for client mode
},
})server (default) | client | |
|---|---|---|
| Mechanism | Next.js draft mode + RefreshRouteOnSave — re-fetches the autosaved draft and re-renders the server component | useLivePreview — receives the editor's unsaved form state via postMessage and re-renders in the browser |
| Updates after | the autosave debounce (~1s) | every keystroke, in real time |
| Fidelity | full — the exact server render, all relationships and server-only logic | high — re-populates relationships to depth; a few server-only niceties degrade in the preview (see below) |
| Needs a save / DB write | yes (autosave) | no |
New projects scaffolded from the Payload template (systhema create, systhema-core payload create-config) set mode: 'client' explicitly. Projects without the option keep the server default.
Both modes leave public (published) pages statically generated — the mode only affects what the editor sees in the preview iframe.
How it worksLink to this section
The preview iframe and the page render are wired by a few pieces that you mostly don't touch:
- Entry — the collection's
admin.livePreview.url. Built bysysthemaPreviewAdmin()(orgeneratePreviewPath()), it points the iframe at/sys/preview?collection=…&id=…&locale=…&path=/your/route&livePreview=true. The persisted document ID remains stable while an editor changes a localized slug or post category. /sys/previewroute (mounted atapp/(site)/(systhema)/sys/[route]/route.ts). It validates the preview secret and explicit locale, authenticates the editor, enables Next.js draft mode, relaxes the draft cookie on a localhost http build (see below), and redirects to the route with its collection, stable ID, locale, and?__livePreview=true. This route is collection-agnostic — it works for built-in and custom collections.- The page render —
<SysthemaLivePreview>. Your route fetches the doc (respectingdraftMode()) and wraps the render. The wrapper readslivePreview.modeon the server and picks the mechanism:- server mode → renders your view; when the editor is previewing, it also mounts
LivePreviewListener(Payload'sRefreshRouteOnSave). On each autosave the admin posts a message, the listener callsrouter.refresh(), and the server component re-fetches the draft. - client mode → renders a tiny client gate. Public visitors get the rendered view unchanged (the route stays SSG). Inside the iframe the gate detects
__livePreviewclient-side (viawindow.location, robust on static routes) and lazy-loads a renderer that callsuseLivePreview: the admin streams the editor's unsaved form state overpostMessage, the hook re-populates relationships via the REST API todepth, and yourview(orclientView) re-renders — no draft cookie, no DB write.
- server mode → renders your view; when the editor is previewing, it also mounts
With frontend locales enabled, the active Payload content locale is part of the preview URL and public path. The /sys/preview entry itself stays unprefixed; after authentication it redirects to the locale-aware catch-all path. The explicit preview locale wins even when the default locale uses unprefixed URLs, so draft fetching, live updates, the shell catalog, links, and metadata all use the language currently selected in the editor.
The built-in Pages view uses exactly this wrapper, and the Pages/Components admin blocks use systhemaPreviewAdmin() — so the public API and the framework's own collections are the same code path. To wire your own collection, see Your own collections.
On localized sites, the shared @systhemaui/next/gateway contract makes the callback's explicit locale authoritative for both Preview and Live Preview. A stale NEXT_LOCALE cookie cannot move the iframe to another language. Prefix routing follows the project's normal as-needed or always policy and can keep a relative iframe on the custom Admin origin; domain routing selects the explicit locale's canonical public origin. Query parameters are preserved in either mode. Built-in page and post renders resolve the stable ID in the exact locale first, then reload display fields with Payload's configured fallback chain; post category slugs remain strict route keys.
With locale domains, mapped public hosts use unprefixed paths and a wrong locale prefix redirects to that locale's canonical host. The explicit Preview locale and every Preview query parameter survive that cross-host redirect. The same domain map drives canonical and alternate metadata, sitemap entries, and Preview/Live Preview targets. The generated route tree and /sys/preview entry do not move or multiply.
Local builds and the draft-mode cookieLink to this section
Server-side live preview relies on the Next.js draft-mode cookie, which a production build sets as Secure; SameSite=None. Over plain http://localhost that Secure cookie is honored by Chrome/Firefox but rejected by Safari/WebKit, and as a SameSite=None third-party cookie it is blocked across a cross-origin admin (customAdminURL) in every browser.
To keep pnpm build && pnpm start usable, Systhema's preview route automatically re-sets that cookie as SameSite=Lax (no Secure) when, and only when, the request is a loopback host (localhost/127.0.0.1/::1) reached over plain http — so server mode live-updates on a locally built same-origin site in every browser. HTTPS deployments (and anything behind a TLS-terminating proxy) are never touched.
The one case this can't cover is a cross-origin admin (customAdminURL) over plain http: a third-party cookie fundamentally needs SameSite=None; Secure, i.e. TLS. There, use client mode (or local HTTPS):
pnpm dev | deployed (HTTPS) | local build, same-origin | local build, cross-origin admin | |
|---|---|---|---|---|
server | ✅ | ✅ | ✅ (auto cookie relax) | ❌ → use client |
client | ✅ | ✅ | ✅ | ✅ |
The cross-origin client cells assume the Admin and iframe origins are allowed by the deployment configuration described below.