Measuring Cache Hit Rates for Wasm Assets

This page answers one task: you have set long cache lifetimes and content-hashed URLs for your WebAssembly modules, but you do not know whether returning users actually load them from cache — or whether frequent deployments, misconfigured CDNs or service workers make most visits download megabytes again. You want numbers from real users and from your delivery infrastructure.

Prerequisites

  • [ ] Modules served from stable, content-hashed URLs.
  • [ ] A way to send small client-side measurements to your analytics or monitoring.
  • [ ] Access to CDN logs or response headers that report cache status.

Three caches, three questions

A Wasm module can come from three places, each with its own hit rate. The browser HTTP cache answers “did this user download it again?”. A service worker cache answers the same question for apps that cache assets themselves. The CDN edge cache answers “did the CDN fetch it from the origin again?”, which affects latency for users who do download and your origin’s load. Client-side measurements see the first two; CDN logs see the third. A complete picture needs both.

Where a module request can be served from A request for the module first checks the browser HTTP cache, then any service worker cache. On a miss it goes to the CDN edge, which serves from its cache or fetches from the origin. Client-side Resource Timing reveals browser and service worker hits; CDN headers and logs reveal edge hits. module request from the page browser HTTP cache hit: no transfer service worker cache hit: from SW CDN edge cache hit or MISS origin server only on edge miss

Step 1 — read Resource Timing in the page

The Resource Timing API exposes per-resource sizes. For a response served from the HTTP cache, transferSize is 0 while decodedBodySize is the module’s size; a network download has a transferSize close to the compressed size plus headers; a revalidation (304) has a small transferSize covering only headers.

function wasmCacheStatus(url) {
  const e = performance.getEntriesByName(new URL(url, location.href).href).at(-1);
  if (!e) return "unknown";
  if (e.transferSize === 0 && e.decodedBodySize > 0) return "cache";
  if (e.transferSize > 0 && e.transferSize < 1024) return "revalidated";
  return "network";
}
report({ wasm: wasmCacheStatus(WASM_URL), release: APP_VERSION });

Cross-origin modules (from a CDN on another host) expose sizes only if the server sends Timing-Allow-Origin; without it, sizes read as 0 and look like cache hits. Add the header for your asset domain.

Step 2 — account for service workers

When a service worker serves the module, Resource Timing still reports the entry, and deliveryType (where supported) or workerStart can indicate it. A simpler approach is to have the service worker itself count cache hits and misses and report them, since it knows which path it took. Report both sources with the same release identifier.

Reading Resource Timing for a Wasm request A transferSize of zero with a decoded size indicates the browser HTTP cache. A small transferSize under about a kilobyte indicates a revalidation returning 304. A transferSize close to the compressed module size indicates a network download. Zero sizes for cross-origin modules without Timing-Allow-Origin are unknown, not hits. transferSize decodedBodySize meaning 0 module size HTTP cache hit < ~1 KB module size revalidated (304) ≈ compressed size module size network download 0 0 cross-origin without Timing-Allow-Origin

Step 3 — read CDN cache status

Most CDNs add a response header such as CF-Cache-Status, X-Cache or Age indicating whether the edge served from cache. Client code cannot always read these for cross-origin responses, so use CDN logs or analytics, filtering by .wasm paths: edge hit ratio, origin fetches per hour, and bytes served. A low edge hit ratio for immutable, hashed files usually means the CDN is not caching them (missing cache rules for the .wasm extension, Vary headers that fragment the cache, or query strings that vary per request).

Step 4 — report hit rates per release

Aggregate client reports into: share of page loads where the module came from the HTTP cache, from a service worker, revalidated, or downloaded — per release and per returning versus new visitors. Right after a release, almost every returning user downloads the new module (its URL changed); over the following days, the hit rate should climb towards the share of returning visits. A hit rate that never climbs points to a caching problem; one that collapses with each deployment points to deployment frequency or non-reproducible builds changing the hash unnecessarily.

Step 5 — fix what the numbers show

Typical fixes: add long Cache-Control headers for hashed .wasm files where they were missing; stop appending per-load query strings; make builds reproducible so unchanged code keeps its hash; split stable and frequently changing code into separate modules; configure the CDN to cache .wasm and to ignore irrelevant query parameters; and stop service workers from re-fetching assets they already hold.

What a good hit rate looks like

For a site with many returning visitors and weekly releases, the share of returning visits loading the module from cache is often well above 80% between releases. Daily releases that change the module lower it substantially; that is a cost worth knowing when deciding release cadence for large modules.

Connecting hit rates to user experience

A cache hit rate is a means, not an end; what users feel is time to a ready feature. Join the cache status with load timing in the same report — time to module ready split by “cache”, “revalidated” and “network” — and the cost of each miss becomes visible in milliseconds on real devices and networks. That turns abstract percentages into a concrete argument: “each deployment that changes the module costs returning mobile users 1.4 seconds on their next visit”. It also shows when caching is not the bottleneck: if cache hits and misses take similar time, compilation or initialisation dominates, and work belongs there instead (code caching, smaller modules, snapshots).

Release-aware analysis

Because hit rates depend so strongly on releases, plot them against deployment events. After each release that changes the module, the hit rate for the new URL starts at zero and rises as returning users fetch it once; the shape of that curve shows how quickly your user base turns over. Releases that do not change the module should not cause a dip at all — if they do, something in the build or deployment is altering the module’s URL. Annotating dashboards with deployments, and marking whether each changed the module’s hash, makes these patterns obvious at a glance.

Server-side corroboration

Origin and CDN logs offer a second view: requests for each module URL per day, split by edge cache status. A steady stream of origin fetches for a hashed, immutable file is a configuration smell. Combining client and server views also catches clients that never report — older browsers, blocked analytics — so neither data source alone misleads you.

Segmenting the data

Averages hide the users who suffer most. Split hit rates and load times by device class, connection type (where the Network Information API is available), country and new versus returning visitors. Mobile users on slow networks gain the most from cache hits and often have lower hit rates because browsers evict more aggressively on devices with little storage.

Expected output

A dashboard shows, per release, that 84% of returning page loads take the module from the HTTP cache, 6% revalidate and 10% download; the CDN edge hit ratio for .wasm is 99.2%; a deployment that changed the module’s hash without code changes is traced to a timestamp embedded by the build; and fixing it raises the hit rate after routine releases.

Gotchas

  • Missing Timing-Allow-Origin on asset domains. Cross-origin sizes read as zero. Add the header.
  • Counting zero sizes as hits. Unknown is not a hit. Check the origin case.
  • Ignoring revalidations. They cost round trips. Use immutable caching for hashed files.
  • Non-reproducible builds. Unchanged code gets new URLs. Make builds deterministic.
  • CDN not caching .wasm. Origin load and latency rise. Add cache rules.
  • Releases that dip hit rates without changing code. Something alters the URL. Annotate deployments with hash changes.

Performance note

Fixing a build that embedded a timestamp in the module raised the returning-visitor cache hit rate from 41% to 84% under daily deployments, saving about 1.8 MB of download per affected visit.

Returning-visit cache hit rate before and after the fix Percentage of returning page loads that loaded the Wasm module from the browser HTTP cache with a build that changed the module hash on every deployment and after making builds reproducible. returning-visit hit rate (%) hash changed every deploy 41 % reproducible builds 84 %

Frequently Asked Questions

Does transferSize include compression? Yes — it reflects bytes over the network, including headers, after compression.

Can I detect code-cache hits? Not directly; compare instantiation time distributions for cache hits versus downloads.

Should I sample these reports? Yes for high-traffic sites; a small percentage gives stable rates.

Do private windows skew results? They always start cold; segment or exclude them if your analytics can tell.

How do I show the cost of cache misses? Report time to module ready split by cache status; the difference between hits and downloads is the cost per miss.

Why are hit rates lower on phones? Browsers evict caches more aggressively on devices with little free storage, and mobile users clear data more often.

Should non-module releases affect the hit rate? No — if they do, the build or deployment is changing the module’s URL unnecessarily.

What does a steady stream of origin fetches for a hashed file mean? The CDN is not caching it — check cache rules for .wasm, Vary headers and query strings.

Which users benefit most from better caching? Returning mobile users on slow networks, where each avoided download saves the most time.

← Back to Module Caching & Startup Performance