CDN Integration
PRISM works well with CDNs for edge caching of rendered pages. This page covers cache keying, Vary headers, and provider-specific configuration.
Cache Keying with X-Prism-Variant
PRISM sets an X-Prism-Variant response header on every rendered response — bot-mobile, bot-desktop, human-mobile or human-desktop — it combines bot detection with device class, and it is set on every rendered response, not only when viewport-aware rendering is on. Your CDN must keep separate cache entries per variant, or a mobile render may be served to a desktop bot and vice versa.
Bypassed pages declare the same dimensions
Since 1.4.0, a response PRISM proxied rather than rendered carries the same
Vary as the rendered one — but only on routes that render for somebody.
In bot-only mode such a URL has two representations: what a crawler is
rendered and what a person is proxied. Only the rendered one used to say so, so
an edge cache could store the human copy against the bare URL and later answer a
crawler from it. PRISM never sees that request, so nothing downstream of the
mistake can correct it.
Routes that never render — assets, excluded paths, non-GET methods — declare nothing extra, so edge caching for them is unchanged.
Vary does not work for this
:::danger Do not put X-Prism-Variant in Vary
Vary names request header fields. It is how a cache decides which stored response may answer an incoming request. X-Prism-Variant is something PRISM writes onto the response, and no client sends it — so a cache looks for it on every request, never finds it, and concludes every variant is the same object. That is the exact collapse the header exists to prevent, made invisible by the header meant to prevent it.
This was PRISM's own default until 1.3.0, and it was wrong for the same reason. :::
PRISM instead advertises Vary: User-Agent, because the User-Agent is what actually changes the body: it selects the device viewport, and in bot-only mode it decides whether the client receives a render or the origin's own bytes.
Making the edge cache well
User-Agent is close to unique per client, so a CDN keying on it caches almost nothing. That is the honest default, not the intended production setup. To get useful edge caching, normalise the User-Agent at your edge into one low-cardinality request header and tell PRISM to vary on it:
[cache]
vary = ["x-device-class"] # the request header your edge injects
The name must not begin with x-prism-. Those are PRISM's own response
headers, and startup refuses them as cache-key dimensions — naming this one
x-prism-variant both fails to start and contradicts the warning above,
because it reuses one name for a response header nobody sends and a request
header your edge does.
PRISM then keys its own cache on that header, forwards it to Chrome, and advertises it in Vary — so the edge configuration works end to end. Without the [cache] vary entry the header is injected and then ignored. See the Reverse Proxy page for edge examples.
PRISM Cache Headers
PRISM includes these headers on rendered responses:
| Header | Description |
|---|---|
X-Prism-Cache | Cache status: HIT, MISS, STALE, or BYPASS |
X-Prism-Render-Time | Render duration in ms (on MISS only) |
X-Prism-Variant | bot-mobile, bot-desktop, human-mobile or human-desktop |
Cache-Control | Synthesized from configured TTL if origin did not provide one |
Cloudflare
Cache Rules
Create a Cache Rule to vary by the X-Prism-Variant header:
- Go to Caching > Cache Rules.
- Create a rule matching your rendered paths (e.g., hostname equals
example.com). - Under Cache key, add Header >
X-Prism-Variant.
Page Rules (Legacy)
If using legacy Page Rules, Cloudflare does not support custom Vary headers. Use a Cache Rule or a Worker instead.
Worker-Based Cache Key
For full control, use a Cloudflare Worker to include the variant in the cache key:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
// Derive the variant from the *request*. Reading X-Prism-Variant here would
// always miss — PRISM sets it on the response, so nothing sends it inbound,
// and every variant would collapse onto the 'desktop' fallback.
const ua = request.headers.get('User-Agent') || '';
const isBot = /googlebot|bingbot|gptbot|claudebot|slurp|facebookexternalhit|twitterbot/i.test(ua);
const isMobile = /android|iphone|ipad|mobile/i.test(ua);
const variant = `${isBot ? 'bot' : 'human'}-${isMobile ? 'mobile' : 'desktop'}`;
const cacheKey = new Request(request.url + '?_variant=' + variant, request);
return fetch(cacheKey);
}
Fastly
VCL Configuration
X-Prism-Variant is a response header — it does not exist at request time,
so hashing req.http.X-Prism-Variant in vcl_hash keys every request on an
empty string and collapses all variants into one object (the same trap as
putting it in Vary). Key on the request header your edge injects instead,
the one you listed in [cache] vary:
sub vcl_recv {
# Normalise the client population into a bounded request header.
if (req.http.User-Agent ~ "(?i)mobile|android|iphone") {
set req.http.X-Device-Class = "mobile";
} else {
set req.http.X-Device-Class = "desktop";
}
}
sub vcl_hash {
set req.hash += req.http.X-Device-Class;
}
With vary = ["x-device-class"] in PRISM's [cache] section, PRISM keys on
the same header and advertises it in Vary, so the edge and PRISM agree end
to end.
Surrogate Keys
For targeted purging, PRISM's response URLs can be used as surrogate keys. Configure Fastly to purge by URL pattern when content changes.
Akamai
Property Manager
- Add a Modify Outgoing Response Header behavior to include
Vary: X-Prism-Variant. - Or use a Cache ID Modification behavior to add the
X-Prism-Variantheader value to the cache key.
Cache Key
In your property configuration:
Cache Key: URL + X-Prism-Variant header
Bot-Only Mode CDN Considerations
In bot-only mode, only bot requests are rendered. This means:
- Bot traffic hits PRISM, gets rendered HTML, and the CDN caches it.
- Human traffic bypasses PRISM and gets the SPA shell directly from the origin.
If your CDN caches both bot and human responses at the same URL, you need to ensure they are cached separately. Options:
1. Separate Bot Traffic at the CDN Edge
Route bot traffic to PRISM and human traffic directly to the origin at the CDN level. Most CDNs can match User-Agent patterns in routing rules.
2. Don't CDN-Cache PRISM Responses
If bot traffic volume is low, skip CDN caching for rendered pages and let PRISM's built-in cache handle it:
Cache-Control: private, no-store
3. Use Vary: User-Agent (Not Recommended)
Adding Vary: User-Agent causes the CDN to create a separate cache entry for every unique User-Agent string, which effectively disables caching. Avoid this approach.
Cache Purging
When content changes on your origin, purge both PRISM's internal cache and your CDN cache:
-
PRISM cache: Send a POST to the admin API:
curl -X POST http://localhost:4001/purge/pattern -d '{"pattern": "/products/*"}' -
CDN cache: Use your CDN provider's purge API. If using viewport-aware rendering, remember to purge both mobile and desktop variants.