Docs

This page isn't translated yet

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
MechanismNext.js draft mode + RefreshRouteOnSave — re-fetches the autosaved draft and re-renders the server componentuseLivePreview — receives the editor's unsaved form state via postMessage and re-renders in the browser
Updates afterthe autosave debounce (~1s)every keystroke, in real time
Fidelityfull — the exact server render, all relationships and server-only logichigh — re-populates relationships to depth; a few server-only niceties degrade in the preview (see below)
Needs a save / DB writeyes (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:

  1. Entry — the collection's admin.livePreview.url. Built by systhemaPreviewAdmin() (or generatePreviewPath()), 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.
  2. /sys/preview route (mounted at app/(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.
  3. The page render — <SysthemaLivePreview>. Your route fetches the doc (respecting draftMode()) and wraps the render. The wrapper reads livePreview.mode on the server and picks the mechanism:
    • server mode → renders your view; when the editor is previewing, it also mounts LivePreviewListener (Payload's RefreshRouteOnSave). On each autosave the admin posts a message, the listener calls router.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 __livePreview client-side (via window.location, robust on static routes) and lazy-loads a renderer that calls useLivePreview: the admin streams the editor's unsaved form state over postMessage, the hook re-populates relationships via the REST API to depth, and your view (or clientView) re-renders — no draft cookie, no DB write.

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.

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 devdeployed (HTTPS)local build, same-originlocal 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.