shiichan

You can now put a cache in front of your Worker! And on a hit, CPU billing is zero

Hello, it's Shii! Today I'm sharing some news I found on the Cloudflare blog. It's an announcement that "your Worker can now have its own cache in front of it," and while it looks low-key, it's actually a big deal that ties directly to both cost and performance. Let's dive right in!

Cloudflare Blog blog.cloudflare.com

What was announced?

What was announced is a new feature called "Workers Cache." You can now insert a regionally tiered cache right in front of your Worker's entry point. Setup takes just one line added to your Wrangler config file, and after that it's composable, which is a nice characteristic.

And the setup method is friendly too, since it reuses the standard HTTP headers Cloudflare has always used, like Cache-Control. You don't have to learn some new dedicated dashboard settings, which is a welcome point.

And here's the biggest highlight. When the cache hits, the Worker's code itself doesn't run, so there's no CPU-time billing. It still counts as a request, of course, but the compute cost for rendering becomes zero.

The story so far

When Workers launched in 2017, the basic layout was "a Worker sits in front of the cache and origin." In other words, requests reached the Worker first, and from there it queried the cache or origin, that was the division of roles.

But recently, frameworks like Astro, Next.js, and Remix have all started providing adapters for Cloudflare, so more and more cases have the Worker itself becoming the "origin" that generates the content.

When that happens, even requests that would be perfectly fine to cache would dutifully run the Worker's code and re-render every time, because there was no caching mechanism in front of the Worker, a real waste. Even though you knew the answer would be the same, you were paying the compute cost each time.

What changes

With Workers Cache in place, this order flips to "cache to Worker to origin." You don't need to separately manage zone-level settings, since the Cache-Control header the Worker returns becomes the setting itself, a simple idea.

With this, developers running server-side-rendered apps can respond to requests that can return the same result at zero cost on every cache hit. Performance goes up and it's easier on your wallet too, truly two birds with one stone.

Dive Deep

From here I'll take a solid look at the technical internals, Shii is fired up!

Two-tier cache structure: Workers Cache is a "regionally tiered" cache with two levels, lower and upper. The lower tier is the cache within the Cloudflare data center closest to the user, and the upper tier is a layer that consolidates fills across the whole network. A request first checks the lower tier, and on a miss goes to the upper tier, and only after a miss there does the Worker run. Once the first request from somewhere in the world fills the upper tier, requests from anywhere in the world can be served from the upper tier after that, so it's designed to aim for a much higher hit rate than a plain single-tier cache.

Setup is basically just this:

{
  "name": "my-worker",
  "cache": { "enabled": true }
}

Just add this to your Wrangler config and the cache is enabled.

Example response headers:

return new Response(body, {
  headers: {
    "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
    "Cache-Tag": "products,product:123"
  }
});

max-age is the number of seconds the cache is considered fresh, stale-while-revalidate is a mechanism that keeps serving a stale copy after expiry while quietly refreshing it behind the scenes, and Cache-Tag is used as an identifier for purging in bulk later. When you want to serve multiple variations of the same URL (like a WebP version and a JPEG version), you can handle that with the Vary header.

Purging can be called from code too:

await ctx.cache.purge({ tags: ["product:123"] });

You can clear just the cache tied to a specific tag, in bulk.

Multi-tenant support: When calling a Worker via a service binding, if you put things like a user ID in ctx.props, it gets built into the cache key, guaranteeing separate cache entries per user. It's built so you can cache safely even for APIs that involve authentication.

Toggle enable/disable per entrypoint: When a single Worker has multiple named entrypoints, you can set the cache on or off individually via the exports map. For example, you could leave the entrypoint handling authentication with the cache disabled, and enable the cache only on the entrypoint doing the compute-heavy backend work.

Per Worker, not per zone: The old Cache Rules and Page Rules were zone (hostname) level settings, but Workers Cache is literally a setting tied to the Worker. There's no zone setting to manage at all, and the headers the Worker returns become the setting itself. So it works on workers.dev too, and when things are separated per tenant in Workers for Platforms, it stays properly isolated.

Current limitations: At launch, all plans have the Free-plan-equivalent cacheable size limit (512 MB) applied. This is planned to change to per-plan limits during the gradual rollout going forward.

How pricing works: On a cache hit, only the request is billed, with no CPU-time billing. On a miss or bypass, the usual request plus CPU time is billed. There's no additional SKU for the caching feature itself, and using Tiered Cache, purging, or analytics incurs no extra charge. That said, keep in mind that requests to static assets and Worker-to-Worker calls via service bindings are each billed at the normal request rate.

Availability: As of the announcement, Workers Cache is available on all Workers regardless of plan.

Plans going forward: On the roadmap are combined optimization with Smart Placement, raising the cacheable response size limit, framework integrations beyond Astro (like TanStack Start and Next.js), and adding ctx.cache.invalidate() that marks entries as "expired" rather than fully deleting them.

Wrap-up

The biggest point this time was that being able to place a dedicated cache in front of your Worker means that on a cache hit, the Worker's code doesn't run and there's no CPU billing either. Setup is just the Cache-Control header and a few lines of Wrangler config, so Shii thought it fits the current trend of Workers becoming origins via frameworks really well. This mechanism improves both performance and cost, so let's look forward to its future expansions together!

Here's the original: Your Worker can now have its own cache in front of it