Skip to content

Research: Workers Cache for thumbnails (2026-07-07)

Date: 2026-07-07 · Primary Cloudflare docs + the launch blog validated against the current Worker. No code changed.

Cloudflare shipped a new Workers Cache on 2026-07-06 — distinct from the Cache API (caches.default) the Worker already uses as edge-cache layer 1. It’s a one-line wrangler flag ("cache": { "enabled": true }) that serves a cached response before the Worker runs, keyed on URL path+query — not on Referer.

For the thumbnail Worker, whose job is deciding clean-vs-watermark from the referrer at request time, enabling it globally on the image path would recreate the referrer-blind cache lottery the product already moved off of (the first requester’s variant pins per-URL-per-colo — the exact failure the delivery-flip research and the Snippet-as-control-point decision resolved). The genuinely valuable part is its request collapsing (single-flight) and stale-while-revalidate, which are free on any plan and answer the open miss-coalescing question in Caching & delivery.

The clean move: do not enable it globally now; sequence it after the Snippet encodes the variant in a distinct URL. Once the URL fully determines the variant, caching-before-the-Worker stops being a lottery and collapsing + SWR + zero-CPU hits become upside.

The Worker already uses a Workers cache — just not this one (confirmed)

Section titled “The Worker already uses a Workers cache — just not this one (confirmed)”

Layer 1 today is the Cache API (caches.default) with a variant-aware key: the watermarked variant appends an internal ?_wm=1. This is the standard-plan way to get custom keys without the Enterprise cf.cacheKey. The new feature is a different mechanism at a different position (in front of the Worker, not called from inside it).

The new Workers Cache is “cache in front of the Worker” (confirmed — Cloudflare docs + blog, 2026-07-06)

Section titled “The new Workers Cache is “cache in front of the Worker” (confirmed — Cloudflare docs + blog, 2026-07-06)”
  • Enable: "cache": { "enabled": true } + a current compatibility_date; any plan, available now.
  • Hit → the Worker does not run. Cached response served directly, zero CPU billed. Miss → the Worker runs; the response is stored if cacheable per its Cache-Control.
  • Request collapsing (single-flight): many concurrent requests for the same cache key run the Worker once, per key per data center; streaming responses join the in-flight stream. Cloudflare calls this the most significant difference from the Cache API, which does not collapse concurrent requests.
  • Stale-while-revalidate via Cache-Control (max-age=…, stale-while-revalidate=…): serve stale instantly, refresh in the background.
  • Only GET/HEAD are cached (they share one entry); other methods always invoke the Worker.
  • Cache key = path + query (order and trailing slash matter) + target entrypoint + a few anti-poisoning headers + Cloudflare-Workers-Version-Key + ctx.props (service-binding/RPC). Excluded: HTTP method, host, body, and standard headers — including Cookie, Authorization, and Referer.
  • Version-scoped by default: each deployed version has its own cache; share across versions with "cache": { "cross_version_cache": true }.
  • Variance is via the response Vary header (a separate variant per exact header value; Vary: * disables caching). Off-Enterprise custom keys now exist via cf.cacheKey when invoked through ctx.exports, and caching can be toggled per entrypoint.

Why this collides with the watermark model (confirmed mechanism)

Section titled “Why this collides with the watermark model (confirmed mechanism)”

The Worker serves different bytes for the same URL depending on the requester’s referrer (cohort → watermark, else clean). Workers Cache keys on URL and the Worker doesn’t run on a hit, so it can’t make that per-request decision — the first variant generated for a URL is served to everyone at that colo until it expires. Vary: Referer can’t rescue it (inference, well-grounded): Referer is per-source-URL high-cardinality, so it explodes into near-infinite variants with ~0% hit rate, and it’s exact-value — not host- or cohort-aware, which is what the clean/watermark split needs. This matches the standing finding that referrer-keyed caching needs a control point that runs before any Cloudflare cache — which is the Snippet.

It answers an open problem we already have (confirmed the gap; inference the fit)

Section titled “It answers an open problem we already have (confirmed the gap; inference the fit)”

Caching & delivery leaves miss-path coalescing / single-flight open, and it’s flagged as possibly needing a new paid primitive. Workers Cache’s request collapsing gives exactly that, free, on any plan. On a cold or freshly-purged URL today, N concurrent misses each fetch the source, run the watermark transform, and write R2 — duplicate expensive work that collapsing would dedupe to one.

Billing changes — the spend rule applies (confirmed rule + mechanism; numbers TBD)

Section titled “Billing changes — the spend rule applies (confirmed rule + mechanism; numbers TBD)”

With caching enabled, every request bills at the standard Workers request rate whether served from cache or the Worker, but CPU is billed only on miss/bypass. Net direction is numbers-dependent: watermark renders are CPU-heavy, so zero-CPU hits push cost down; but any hits currently served for free by the edge before the Worker would convert into billed Worker requests, pushing cost up. Per the standing rule, a change likely to move Cloudflare spend ≥ $1/month needs a written estimate first — required before rollout.

  1. Shared fallback graphics (_shared/error.jpg, _shared/locked.jpg): one object serves many IDs and is variant-stable per URL → safe to front; and collapsing helps during error storms.
  2. A fixed-variant endpoint (a test route, or a future always-clean / always-watermark path): URL determines variant → safe, and gains collapsing + SWR + zero-CPU hits.
  3. Post-Snippet main path: once the Snippet encodes the watermarked variant as a distinct URL and the bare URL is always clean (Edge watermark router), variant ∈ URL → Workers Cache becomes safe and high-value everywhere, and can largely replace the hand-rolled caches.default layer.
  4. SWR for negative/fallback refresh — overlaps the existing R2 soft/hard-TTL negative cache, so treat as a possible simplification, not an addition.
  • Do not set "cache": { "enabled": true } globally while the referrer-based clean/watermark decision still lives inside the Worker and the variant is not in the URL — it reintroduces the cache lottery and undoes deterministic delivery.
  • Do not try to preserve variance with Vary: Referer — cardinality blowup, ~0% hit rate, not cohort-aware.
  • Do not stack Workers Cache on top of an unresolved referrer-blind edge cache rule; resolve that first.
  • Do not ignore version-scoping: gradual rollouts start cold per version unless cross_version_cache is set — decide deliberately.
  • Do not roll out without the written cost estimate.
  1. Now: don’t enable globally; record this reasoning.
  2. Cheap win, low risk: pilot Workers Cache on a fixed-variant surface (shared fallback graphics and/or a test route) to bank request-collapsing + SWR and measure the billing delta on a small slice — which produces the required cost note.
  3. Sequenced main-path adoption: fold Workers Cache into the Snippet URL-encoded-variant rollout; then enable it on the image path, set cross_version_cache as appropriate, and retire the manual caches.default layer for that path.
  4. Keep the Snippet as the variant control point. Workers Cache complements it; it does not replace referrer determinism.
  • Exact billing delta for this traffic mix (needs the current request vs edge-hit vs CPU-render split) — gates rollout.
  • Does resolving the referrer-blind edge rule plus Workers Cache double-count or simplify the edge layer? Confirm before enabling both.
  • Should the negative-cache/fallback path move to SWR semantics, or stay on its bespoke R2 soft/hard TTLs?

Primary (Cloudflare):

Internal (not published): the Worker cache implementation (apps/thumbnail/lib/cache.ts), the miss-path plan, and the Snippet variant-flip plan are repo docs — pointed to, not copied.