This project can demonstrate stale-while-revalidate (SWR) in two different ways:
- Native Cloudflare CDN cache behavior.
- A custom Worker implementation using the Workers Cache API.
They look similar from the client side, but the cache layer doing the work is different.
Native CDN SWR relies on Cloudflare's normal CDN cache interpreting HTTP cache directives from the origin response.
Example response header:
Cache-Control: max-age=5, stale-while-revalidate=60Semantics:
0–5s: response is fresh and can be served from cache.5–65s: response is stale but may be served immediately while Cloudflare revalidates in the background.>65s: response is too stale and must be refreshed before serving.
The fetched URL must be handled by Cloudflare's CDN cache for the zone whose cache policy should apply.
For a Worker subrequest:
await fetch("https://api.example.com/mix/s=200/d=3");Cloudflare applies cache rules based on the subrequest URL (api.example.com), not the original visitor URL that invoked the Worker.
If api.example.com is another active/proxied Cloudflare zone, that zone's cache configuration applies.
If the origin does not emit the desired header, a Cache Response Rule can modify the origin response before it is cached, for example setting:
Cache-Control: max-age=5, stale-while-revalidate=60Use this when you want Cloudflare's CDN cache to perform SWR natively.
- This does not work in
wrangler dev/localhost:8787because local development does not run zone-level Cache Rules or Cache Response Rules. - Cache Response Rules require the hostname to be proxied through Cloudflare.
- Avoid
cf.cacheTtl/ Edge TTL overrides when testing native SWR, because Edge TTL-style overrides can disable or bypass revalidation directive behavior. - For cross-zone Worker subrequests, put the Cache Response Rule on the fetched hostname's zone, not necessarily the initiating Worker's zone.
- Uses Cloudflare's CDN cache as intended.
- Supports standard cache observability such as
CF-Cache-Status. - Can use CDN-level features such as Tiered Cache.
- Less Worker code.
- Better for production when the cacheable asset is naturally an HTTP resource behind Cloudflare.
- Requires a deployed/proxied Cloudflare zone.
- Harder to demonstrate locally.
- Behavior depends on zone configuration, cache eligibility, rules, and response headers.
- Less explicit in application code.
- Cross-zone behavior can be confusing: cache rules apply to the fetched URL's zone.
The custom implementation uses caches.default as a storage layer and implements SWR logic directly in Worker code.
The Cache API does not natively support stale-while-revalidate or stale-if-error directives on cache.match() / cache.put().
Instead, the Worker stores metadata such as:
X-SWR-Stored-At: <timestamp>Then it manually decides whether a cached response is fresh, stale-but-servable, or expired.
Example logic:
const cachedResponse = await cache.match(cacheKey);
if (cachedResponse) {
const ageSeconds = getAgeSeconds(cachedResponse);
if (ageSeconds <= MAX_AGE_SECONDS + STALE_WHILE_REVALIDATE_SECONDS) {
if (ageSeconds > MAX_AGE_SECONDS) {
ctx.waitUntil(refreshCache(cache, cacheKey));
}
return cachedResponse;
}
}
return refreshCache(cache, cacheKey);With:
const MAX_AGE_SECONDS = 5;
const STALE_WHILE_REVALIDATE_SECONDS = 60;The Worker enforces:
0–5s: serve cached response as fresh.5–65s: serve cached response immediately and refresh in the background withctx.waitUntil().>65s: block on a new upstream response.
The Cache API is not interpreting the stale-while-revalidate directive.
The Worker code is doing the SWR decision-making itself. caches.default is only storing and retrieving responses.
So this does not contradict the documentation. The unsupported behavior is automatic SWR inside cache.match() / cache.put().
For a robust custom implementation, the response stored in Cache API should remain available for the whole stale window.
Conceptually:
Internal Cache API TTL: 65s
Client Cache-Control: max-age=5, stale-while-revalidate=60Then the Worker code, not the Cache API, decides whether the entry is fresh or stale.
- Works in local/demo-style Worker code without depending on zone-level Cache Response Rules.
- Fully explicit application behavior.
- Can cache generated Worker responses, not only origin responses.
- Can implement custom policies beyond standard HTTP cache semantics.
- Easy to add debugging headers such as
X-Cache: HIT | MISS | STALE.
- More code and more application responsibility.
- Cache API SWR is manual, not native.
- Cache API operations are local to the data center handling the request.
- Cache API does not support Tiered Cache.
- More care is needed around TTLs, cache keys, request methods, response headers, errors, and concurrent refreshes.
Yes.
Cloudflare documentation states that the Workers Cache API is not compatible with Tiered Cache. Cache API operations apply to the cache in the data center where the Worker is running.
That means:
cache.match()checks the local data center cache.cache.put()stores in the local data center cache.cache.delete()deletes from the local data center cache.
By contrast, fetch()-based CDN caching can use Cloudflare's normal CDN cache path and Tiered Cache when enabled.
Use native CDN SWR when:
- The resource is an HTTP origin response behind Cloudflare.
- You can configure the relevant zone's Cache Rules / Cache Response Rules.
- You want CDN-level behavior, observability, and Tiered Cache.
Use custom Cache API SWR when:
- You need local/demo behavior.
- The response is generated or transformed by the Worker.
- You need custom SWR behavior that is easier to express in code than in cache rules.
- You accept that caching is local to each data center and does not use Tiered Cache.