Skip to main content

Response Headers

PRISM adds several headers to HTTP responses to indicate rendering status, cache state, and variant information. These headers are useful for debugging, CDN cache keying, and monitoring.

PRISM strips any x-prism-* headers from origin responses before adding its own, preventing origin-side forgery.

Rendered Responses​

These headers appear on responses that were rendered through the Chrome pipeline:

x-prism-rendered​

x-prism-rendered: true

Present on every response that was rendered by PRISM via headless Chrome. Absent on proxied (bypass) responses.

x-prism-cache​

x-prism-cache: HIT|MISS|STALE|BYPASS

Indicates the cache status of the response:

ValueMeaning
HITServed from the render cache
MISSFreshly rendered and now cached
STALEServed stale from cache while re-rendering in the background
BYPASSNot rendered; proxied directly to origin

This header is present on all responses, including bypass responses.

STALE requires that serving stale is permitted. An origin that sent must-revalidate, proxy-revalidate or s-maxage has forbidden it — s-maxage carries proxy-revalidate semantics for shared caches (RFC 9111 §5.2.2.10) — so entries carrying those expire outright rather than entering the grace window configured by cache.grace_period_secs.

Cache-Status​

Cache-Status: prism; hit

The same fact as x-prism-cache, in the interoperable field RFC 9211 defines, so a CDN, a browser devtools pane or an engineer reading someone else's stack can see it without knowing PRISM exists. Both are sent; x-prism-cache predates the standard and deployments read it, so it is not going anywhere.

SituationCache-Status
served from cacheprism; hit
served stale while re-renderingprism; hit; detail=stale
rendered because nothing was storedprism; fwd=miss
not rendered at allprism; fwd=bypass

Stale is reported as a qualified hit rather than fwd=stale, because the reader was answered from the cache — forwarding is precisely what serving stale avoids.

There is no ttl parameter. RFC 9211 makes every parameter optional, and the remaining freshness is derivable from the Age and Cache-Control sent alongside it. A third statement of the same number would be one more thing that can disagree with the other two.

x-prism-render-time​

x-prism-render-time: 1250

The time in milliseconds that the Chrome render took. Only present on cache MISS responses (fresh renders). Not included on HIT or STALE responses since no render occurred.

x-prism-variant​

x-prism-variant: bot-mobile|bot-desktop|human-mobile|human-desktop

The rendering variant used for this request. Determined by combining bot detection with device detection (when viewport-aware rendering is enabled):

VariantDescription
bot-mobileBot user-agent on a mobile viewport
bot-desktopBot user-agent on a desktop viewport
human-mobileHuman user on a mobile viewport
human-desktopHuman user on a desktop viewport

x-prism-fallback​

x-prism-fallback: true

Present when PRISM served the origin's own response instead of a render. The value says why:

ValueMeaning
trueThe render was attempted and failed — Chrome crash, timeout, content validation — so the origin's response was served instead. A reader always gets an answer even when the render pipeline is degraded.
not-htmlThe origin returned something that is not HTML on a route that renders. Nothing to render; it is passed through unchanged.
no-transformThe origin sent Cache-Control: no-transform and cache.honor_no_transform is on. The render succeeded and was discarded: the directive forbids serving a transformed representation, not producing one.

The first is a degradation. The other two are PRISM doing what it was told.

Bypass / Unlicensed Responses​

x-prism-license​

x-prism-license: unlicensed

Present on bypass responses when PRISM is running without a valid license. Not present when properly licensed.

CDN Integration Headers​

Vary​

Vary: User-Agent, Accept-Encoding, Accept

Names the request headers that select between representations of a URL, merged with whatever the origin already declared and anything listed in [cache] vary.

User-Agent appears only when it can actually matter — in bot-only mode, where it decides whether a client is rendered or proxied, and when viewports are enabled. It is close to unique per client, so declaring it costs a downstream cache nearly every hit. Operators who want low-cardinality edge caching normalise the User-Agent into one header at the edge and list that header in [cache] vary; PRISM then keys on it and advertises it instead.

X-Prism-Variant is deliberately not listed here. Vary names request headers and nothing sends that one, so a cache looked it up, found it absent on every request, and collapsed every variant into a single object. Use it as a cache-key component instead — see CDN Integration.

Since 1.4.0 this is set on bypassed responses too, when the route is one that renders for somebody. A URL that renders for a crawler and is proxied to a person has two representations, and a downstream cache that stores the second without knowing what separates them will answer the first with it — on a request PRISM never sees and so cannot correct. Routes that never render declare nothing extra, so edge caching for assets and excluded paths is unaffected.

Age​

Age: 47

How many seconds old the response body is, counting time it spent in a cache upstream of PRISM before it arrived. Required of any cache reusing a stored response (RFC 9111 §4).

Without it a downstream CDN starts its own freshness clock from zero and the same seconds are counted on every hop. PRISM also reads the origin's Age: a response arriving with Age: 50 and max-age=60 has ten seconds of freshness left here, not sixty.

Time spent rendering is included, since 1.6.0. The entry's clock starts when it is stored, which is after the render, so the age it starts from accounts for the origin's Date, its Age, and however long the fetch and render took — RFC 9111 §4.2.3. Before that the render was granted for free: on the shipped 30-second error TTL, a five-second render was a sixth of the lifetime nobody had counted.

Cache-Control​

Cache-Control: public, max-age=3600, s-maxage=3600

Synthesized by PRISM only when the origin response includes no Cache-Control of its own and cache.synthesize_cache_control is enabled.

The lifetime announced is the one PRISM actually stores under, which since 1.5.3 depends on what the response is:

responseannounced
an ordinary rendercache.default_ttl or the path-specific rule
a 5xx from the origincache.error_ttl_secs (default 30s), or no-store when that is 0
PRISM's own 429, 502, 503, 504no-store

Before 1.5.3 the route's TTL was announced in every case, so an error stored for thirty seconds was advertised for an hour and anything caching in front of PRISM kept serving it after the origin recovered. See status codes for what is stored and for how long.

When the origin does send one, it is preserved and — since 1.4.0 — obeyed. PRISM's cache is shared and cannot revalidate, so a directive requiring validation before reuse can only be honoured by not keeping the entry at all: no-cache, max-age=0, s-maxage=0 and Vary: * are all refused, alongside no-store and private. Where the origin does grant freshness it caps PRISM's own TTL rather than being overruled by it, s-maxage first.

Before 1.4.0 only no-store and private were read and everything else was forwarded to the client while PRISM itself ignored it.

Header Precedence​

PRISM preserves security and cache headers from the origin response (e.g., Strict-Transport-Security, X-Frame-Options, Content-Security-Policy) but does not override any header it has already set. Origin ETag and Last-Modified headers are not forwarded on rendered responses because the response body has been transformed.

Debugging with Headers​

Use curl -I to inspect PRISM headers without downloading the full response:

# Check if a page is being rendered
curl -sI https://example.com/products/widget | grep -i x-prism

# Expected output for a rendered bot request:
# x-prism-rendered: true
# x-prism-cache: HIT
# x-prism-variant: bot-desktop

To test as a bot:

curl -sI -A "Googlebot/2.1" https://example.com/products/widget | grep -i x-prism