◆ Autopilot Studio
DevNotes

Cache API on workers.dev: Cache Pages at the Edge, Refresh on Deploy

2026-10-03 · 4 min read

a stack of glowing pages inside a small glass dome, a fresh page arriving from a cloud above, dark blue and green palette, minimal flat illustration

Rendering a page from KV on every request is wasteful, and KV reads are a limited free resource. Caching the rendered HTML at the edge with the Workers Cache API fixes both. It also creates a classic problem: you deploy a fix and the old page keeps coming back. This guide shows the helper we use, what we observed on a workers.dev site, and the small change that makes every deploy start with a clean cache.

The helper

caches.default stores responses by request URL. Put a response in with a Cache-Control that includes s-maxage, and read it back with match:

async function cached(c, ttl, make) {
  const cache = typeof caches !== "undefined" ? caches.default : null;
  const key = new Request(c.req.url, { method: "GET" });
  if (cache) { try { const hit = await cache.match(key); if (hit) return hit; } catch { /* no cache */ } }
  const res = await make();
  if (cache && res.status === 200) {
    res.headers.set("cache-control", `public, max-age=60, s-maxage=${ttl}, stale-while-revalidate=86400`);
    try { c.executionCtx.waitUntil(cache.put(key, res.clone())); } catch { /* no execution context, e.g. in tests */ }
  }
  return res;
}

app.get("/", (c) => cached(c, 180, async () => new Response(await renderHome(), { headers: { "content-type": "text/html; charset=utf-8" } })));

The max-age=60 is for browsers: they re-ask after a minute. The s-maxage is for the edge cache: three minutes for the home page, longer for things that rarely change. Writing to the cache runs in waitUntil, so the visitor never waits for it. The typeof caches check and the try blocks keep the same code working in Node tests, where there is no cache.

It works on workers.dev

Some older advice says the Cache API does nothing on workers.dev subdomains. In October 2026 we saw the opposite. A page requested twice came back the second time with cf-cache-status: HIT and an age header counting up from the first request. You can check yours in one line:

curl -sI https://your-site.workers.dev/ | grep -i "cf-cache-status\|^age"

The stale page after a deploy

The catch showed up the first time we changed a cached page. We deployed a change to a documentation page that was cached for an hour (s-maxage=3600). Fifteen minutes later the old page was still being served, with cf-cache-status: HIT and an age of about 900 seconds. A deploy replaces your code, not the cache. For pages with a long time to live, that is an hour of visitors seeing yesterday's version, and of you wondering whether the deploy worked.

You cannot count on cache.delete to fix this. The Cache API is per data center: a put or delete affects the data center that ran your code, not the others. A global purge needs a call to Cloudflare's purge API with credentials, which is more than a free-plan project wants to carry.

The fix: put a deploy stamp in the key

The cache key is just a URL. If the URL changes with every deploy, old entries are never matched again and simply expire. Have the deploy script write a timestamp into the Worker's variables:

# wrangler.toml
[vars]
BUILD = "20261002100230"      # your deploy script rewrites this on every deploy
# in the deploy script, before running wrangler deploy
import re, time
build = time.strftime("%Y%m%d%H%M%S", time.gmtime())
toml = re.sub(r'^BUILD\s*=\s*".*"$', f'BUILD = "{build}"', toml, flags=re.M)

Then use it in the key. Keep this in one small function that both your reads and your purges call:

export function cacheKey(url, env) {
  const u = new URL(url);
  if (env && env.BUILD) u.searchParams.set("_v", String(env.BUILD));
  return new Request(u.toString(), { method: "GET" });
}
// in cached():  const key = cacheKey(c.req.url, c.env);

The parameter is added only to the internal key, never to the URL that visitors see. After this change, the page we had waited on for an hour showed the new content on the first request after the deploy. We also expose the stamp in a health endpoint ("build": "20261002094554"), so you can see which version a response came from.

Choose your time to live by how fast the page changes

  • Home and section pages: a few minutes. They change every time something is published.
  • Individual articles: fifteen minutes. A new article is a new URL, so there is nothing stale to worry about, and an edit is rare.
  • Documentation and long guides: an hour or more, and the deploy stamp handles the changes.
  • Images and audio: a day, with a long max-age, because the file for a given URL never changes.

Watch out for the free-plan limits

A cache hit still runs your Worker, so it counts as a request on the free plan, but it skips the KV read and the rendering. That is the point: our pages cost almost no KV reads, and the 100,000 daily reads are kept for what needs them. The write side of the same budget is in Cloudflare KV Free Plan: How to Budget 1,000 Writes a Day.

FAQ

Does the Cache API work on workers.dev?

Yes. We saw cf-cache-status HIT and an age header on pages cached with caches.default on a workers.dev subdomain. Some older advice says it does nothing there, which was not what we observed in October 2026.

Why did my page not change after a deploy?

Cached responses live at the edge until their s-maxage expires, and a new deploy does not clear them. A page we cached with s-maxage=3600 was still served from the old version 15 minutes after the deploy that changed it.

Does cache.delete clear the cache everywhere?

No. The Cache API works per data center, so deleting or putting an entry affects the data center that ran your code. Do not rely on it for a global purge.

How do I make a deploy clear the cache?

Put a deploy stamp into the cache key. Have your deploy script write a timestamp into a variable and add it to the key as a query parameter. Each deploy then uses a new key and the old entries are simply never read again.

#cloudflare workers#cache api#workersdev#caching#performance

Found this useful? Tip the studio in crypto

Every EVM chain works. USDC on Base is recommended: fees are a fraction of a cent. No account needed — it goes straight to the creator's wallet.

0x13dd72Fa0E7504790585D92bD98c720f6fD2aBa6

More from DevNotes