caches.default is manual; Workers Caching is read-through with purge

The Cache API stores only what you put() and never collapses a herd. Workers Caching fronts an entrypoint, honors Cache-Control, purges by tag.

Workers

· Chapter

61

·

3

min read

The answer. For years the way to cache inside a Worker was the Cache API: caches.default.match(request), and on a miss fetch the origin and cache.put(request, response.clone()). It still works and it still has the same sharp edges: only GET requests, a cache scoped to the data center the Worker ran in, no request collapsing (a burst of misses invokes the origin once per request), and whatever Cache-Control says is what you get, which is how a transient error got pinned for 24 hours in our ingest Worker. Workers Caching is the newer, read-through model: enable cache on an exported entrypoint and Cloudflare caches its responses by URL according to Cache-Control, collapses concurrent misses, participates in tiered caching, and invalidates with ctx.cache.purge({ tags }) from code. Put the gateway logic (auth, header stripping) in an uncached default entrypoint and the cacheable work in a second, cached one.

The pattern.

[cache]
enabled = true
[exports.default]
type = "worker"
[exports.default.cache]
enabled = false                  # gateway: runs every time, strips Authorization
[exports.CachedApi]
type = "worker"
[exports.CachedApi.cache]
enabled = true                   # read-through cache in front of this entrypoint
export class CachedApi extends WorkerEntrypoint<Env> {
  async fetch(request: Request) {
    const data = await loadExpensive(request, this.env);
    return Response.json(data, { headers: { "cache-control": "public, max-age=60, stale-while-revalidate=600", "cache-tag": "products,product:42" } });
  }
}
export default { async fetch(request, env, ctx) {
  if (!(await authenticate(request, env))) return new Response("unauthorized", { status: 401 });
  const fwd = new Request(request); fwd.headers.delete("authorization");   // Authorization forces BYPASS
  return ctx.exports.CachedApi.fetch(fwd);
} };

Watch out.

  • Neither layer caches a response with Set-Cookie, and the Cache API rejects Vary: * and 206s outright.
  • cache.delete() purges only the local data center; global invalidation needs Cache-Tag and a purge-by-tag, or ctx.cache.purge.
  • Cache errors deliberately: give 5xx responses Cache-Control: no-store before any put().

Related: cache-api-poisoned-by-transient-error · kv-cache-upstream-json-with-ttl-and-stale-fallback · r2-serve-objects-with-their-own-headers-etag-range