Preview and revalidation
Two platform signals reach a storefront:
- Preview — an editor frames the storefront and sends it an unsaved draft.
- Revalidation — a publish changed what the pages show, so cached pages must be dropped.
Both are signed requests from another origin. Both fail closed when the deployment has no preview key.
Preview
A preview is a token-bearing GET, then same-origin POSTs with drafts:
Code
The first render shows what is saved. The editor then posts a draft — page document, theme settings, text edits — and the framed page re-renders it. The draft is never written to the database.
Read the request
readPreviewRequest lives on the server entry point and does all the
checking. createStorefrontMiddleware calls it for you on the preview path;
call it yourself only in a middleware you wire by hand:
Code
The result is either { ok: true, claims, draft, token } or { ok: false, status, reason }.
It guarantees:
- GET must carry
token,surface, and optionallylocaleandpath. - POST must come from the storefront's own origin, as JSON, under 1 MB.
- The token is Ed25519 (
EdDSA) and made for this storefront and this theme. - A token made for revalidation cannot open a preview.
- The token lives at most one hour.
- The draft is bounded and shape-checked.
Without a key, every preview request is a 404. That is how preview stays off.
Response headers
Every preview response carries:
Code
| Header | Value | Why |
|---|---|---|
Cache-Control | private, no-store | A draft must never be cached |
Content-Security-Policy | frame-ancestors <editor> | Only the token's editor may frame it |
X-Robots-Tag | noindex, nofollow | A draft must not be indexed |
Referrer-Policy | same-origin | The token in the URL must not leak |
Do not add a route cache rule for the preview path. A cached preview would show one merchant's draft to another visitor.
Render the draft
resolvePreviewStore(draft, { client, fallback }) from
@wocart/storefront/astro builds the store as the draft would leave it: the
draft's theme settings and text edits over the saved ones, in the draft's
language when the store serves it (else the primary), with pixelId null —
a preview is not a visit. Its pathname is the draft's path, else /.
previewConfig(config, draft) is the same overlay for a config you already
hold.
draft.settingsreplacesthemeSettings.draft.contentreplaces the text edits of the languages it names.draft.documentis the page document to render instead of the saved one; an editable theme reads it like a saved document, through@wocart/theme'sresolveSurfaceDocument. This SDK does not interpret section shapes.
A preview route
A preview is rendered by a normal Astro page that is never prerendered. The
middleware opens it before resolving the request's store, because the
draft's surface and language decide the store. createStorefrontMiddleware
does this when given the preview route and key:
Code
A refused token answers its status with Cache-Control: no-store; a
verified one sets locals.store from resolvePreviewStore and
locals.preview to { claims, draft, token }, and never runs a form action.
The page then renders the draft and sets the headers above:
Code
An editable theme completes the page with previewTarget and the preview
bridge; the theme starter has every file
(src/middleware.ts, src/pages/wocart-preview.astro,
src/pages/wocart-revalidate.ts): a signed token opens the preview, a draft posted from the
page's own origin re-renders, a draft posted from another origin is refused
with 403, and a revalidation token is refused on the preview route.
The path /wocart-preview is the platform's (THEME_PREVIEW_PATH in
@wocart/theme): Wocart Studio frames that path.
A preview token is a capability
Any page that holds the token can frame the preview. Keep the URL out of referrers, logs and analytics; the headers above do the first, and your logging must do the rest. The token can only render a draft — it cannot write or purge.
Revalidation
A publish changes the store's page data. The platform posts a signed token to
the theme's revalidation route (REVALIDATE_PATH, /wocart-revalidate), and
the theme drops its cached pages. revalidateEndpoint is the whole route:
Code
POST answers 204 after dropping every page cached under PAGES_CACHE_TAG
for a verified token, and the token's refusal status otherwise, dropping
nothing. ALL answers any other method 405. No answer is cached. A route
wired by hand reads the request with readRevalidateRequest.
The checks:
POSTonly.Authorization: Bearer <jws>.- The token is signed with the deployment key.
usemust be"revalidate"; a preview token is refused.audmust be this storefront origin.thememust be this theme's key.iat/expspan at most five minutes.
A preview token cannot purge a cache; a revalidation token cannot open a preview.
Drop the pages
PAGES_CACHE_TAG ("pages", from @wocart/storefront/server) is the tag
the route drops. Every cached page route in astro.config.mjs's
routeRules must carry it; a route that omits it is not purged on publish.
Code
The revalidation path needs no store: createStorefrontMiddleware lets it
through, and a middleware wired by hand must too, or a purge would depend on
a store read.
Where the token comes from
The platform signs preview and revalidation tokens with its own private key;
the storefront only checks them with the deployment's public key. The storefront
cannot mint either token. WOCART_PREVIEW_KEY is optional: without it, preview
and revalidation are both off and answer 404.
A preview and caching checklist
- The preview route is
prerender = false. - The route is excluded from every cache rule.
- Every preview response sets
previewResponseHeaders. previewConfigis applied before rendering.- The theme reads the preview draft's document; the SDK does not interpret it.
- The revalidation route checks the bearer token and theme.
- Cache pages carry a tag; revalidation invalidates that same tag.
- Both routes answer 404 when no preview key is configured.
- The store's pixel is absent in preview.