Skip to main content

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:

HeaderDescription
X-Prism-CacheCache status: HIT, MISS, STALE, or BYPASS
X-Prism-Render-TimeRender duration in ms (on MISS only)
X-Prism-Variantbot-mobile, bot-desktop, human-mobile or human-desktop
Cache-ControlSynthesized from configured TTL if origin did not provide one

Cloudflare​

Cache Rules​

Create a Cache Rule to vary by the X-Prism-Variant header:

  1. Go to Caching > Cache Rules.
  2. Create a rule matching your rendered paths (e.g., hostname equals example.com).
  3. 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​

  1. Add a Modify Outgoing Response Header behavior to include Vary: X-Prism-Variant.
  2. Or use a Cache ID Modification behavior to add the X-Prism-Variant header 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

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:

  1. PRISM cache: Send a POST to the admin API:

    curl -X POST http://localhost:4001/purge/pattern -d '{"pattern": "/products/*"}'
  2. CDN cache: Use your CDN provider's purge API. If using viewport-aware rendering, remember to purge both mobile and desktop variants.