skip to content

In a service worker's fetch handler, what do the cache-first, network-first and stale-while-revalidate strategies each do, and which one would you apply to hashed JS bundles, the HTML document, and a JSON API response?

level: middleimportance: must knowfreq 78%

answer

  1. the URL's contract picks the strategy
  2. immutable filename versus stable filename
  3. one strategy per route, not per app
  4. cache-first, network-first, stale-while-revalidate
  5. hashed bundle, document, API JSON

basics

~20 s

Cache-first answers from storage and only hits the network on a miss; network-first tries the network and falls back to the stored copy; stale-while-revalidate serves the cached copy instantly and refreshes it in the background. Match them to hashed bundles, HTML, and API JSON respectively.

solid answer

~50 s

A service worker intercepts page requests in its `fetch` handler and decides where each response comes from, so the strategy should follow the URL's contract rather than being applied to everything at once. **Cache-first** checks storage and only goes to the network on a miss — right for content-addressed URLs such as `/app.8f3a1c2d.js`, because that URL can never mean different bytes. **Network-first** tries the network and falls back to the stored copy when the request fails — right for the HTML document, whose URL is stable while its contents change every deploy, so serving it from cache strands users on an old build. **Stale-while-revalidate** returns the cached response immediately, fetches in the background and stores the fresh copy for next time — a good fit for API JSON that must render fast and can be one load behind. The degenerate two are **cache-only** for precached shell assets and **network-only** for anything you must never serve stale.

go deeper

for a junior

Be able to name cache-first, network-first and stale-while-revalidate and say in one sentence what each returns to the page. Knowing that the choice depends on the asset already puts you ahead.

for a middle

Explain the mechanics: what the fetch handler awaits in each strategy, why the body must be cloned before storing, and why an immutable hashed URL and a stable HTML URL demand opposite treatment.

for a senior

Show that you route per asset class rather than globally, that you only store trustworthy responses, and that you can predict the user-visible failure each wrong choice produces after a deploy.

for a principal

Own the contract between the build and the worker: which URLs the pipeline guarantees are immutable, what staleness the product will tolerate per data class, and how that policy is written once and enforced rather than rediscovered per feature.

## What a "strategy" is A service worker is a script the browser runs in the background for an origin. Once it controls a page, requests the page makes fire a `fetch` event in the worker, and `event.respondWith(promiseForResponse)` lets the worker decide what the page receives. A caching strategy is just the shape of that promise: which of `caches.match(...)` and `fetch(...)` you consult, in what order, and what you store afterwards. The five names below come from the literature and from Workbox's `workbox-strategies` package (`CacheFirst`, `NetworkFirst`, `StaleWhileRevalidate`, `CacheOnly`, `NetworkOnly`), but they are patterns, not APIs — you can write each in a few lines by hand. ## The five patterns - **Cache-first** — look in the cache; on a hit return it without touching the network; on a miss fetch, store, return. Fastest possible response and works offline, but the user gets whatever was stored until something else replaces it. - **Network-first** — fetch; if it resolves, return (and usually store a copy); if it rejects, fall back to the cache. Freshest possible response, but every load pays network latency, and offline is a fallback rather than the norm. - **Stale-while-revalidate** — return the cached response now, and in parallel fetch and store a fresh copy for the *next* request. One-load-stale by design. - **Cache-only** — never touch the network. Used for assets you precached at install and guarantee are present. - **Network-only** — never touch the cache. The default for anything you did not route. ```js async function staleWhileRevalidate(event) { const cache = await caches.open('api-v1'); const cached = await cache.match(event.request); const network = fetch(event.request).then((response) => { if (response.ok) cache.put(event.request, response.clone()); return response; }); event.waitUntil(network.catch(() => {})); return cached || network; } ``` Note `response.clone()`: a response body is a stream that can be read once, so you must clone before handing one copy to the page and the other to the cache. ## Why the asset class decides The question an interviewer is really asking is *what does this URL promise?* **Hashed bundles** such as `/app.8f3a1c2d.js` are content-addressed: the build renames the file whenever the bytes change. That makes the URL immutable, and immutable URLs are exactly what cache-first is for. There is nothing to revalidate, so a network round trip would be pure waste. **The HTML document** lives at a stable URL (`/`, `/products/42`) whose content changes on every deploy, and it is the file that names the current bundle hashes. Serve it cache-first and the user keeps loading last month's markup, which points at last month's bundles — the whole app freezes at that build. Network-first keeps the entry point fresh while still rendering something when the user is offline. **API JSON** is a judgment call. Stale-while-revalidate suits read-mostly, tolerant data — a feed, a product list, a dashboard that can visibly update a beat later. Data where staleness is dangerous (balances, permissions, anything the user just wrote) belongs on network-only, or network-first if you want a degraded offline read. ## Routing, not a global rule Because the strategy is per-asset, real service workers branch on the request before responding — commonly on `event.request.mode === 'navigate'` for documents, on `event.request.destination` (`'script'`, `'style'`, `'image'`, `'font'`), or on the URL path. Workbox expresses the same thing as `registerRoute(matcher, strategy)`. A worker that applies one strategy to everything is the single most common design mistake in this area. Two details worth stating out loud: only store responses you can trust (`response.ok`, and skip opaque cross-origin responses whose status you cannot inspect), and remember the worker's own `fetch()` still passes through the browser's HTTP cache, so a "fresh" revalidation can come back from there unless you pass `{ cache: 'no-cache' }` or similar on the request.

  • Why does stale-while-revalidate need response.clone() when it stores the fetched response?
    A `Response` body is a readable stream that can be consumed exactly once. If you pass the original to `cache.put()` and also return it to the page, one of the two gets a body that is already locked or drained. Cloning before either read gives you two independently readable copies of the same response.
  • You route by URL path today. What is a more robust signal for picking a strategy?
    The request itself carries intent: `event.request.mode === 'navigate'` identifies document navigations, and `event.request.destination` reports `'script'`, `'style'`, `'image'`, `'font'`, `'document'` and so on. Routing on those survives URL restructuring and does not need a regex per asset folder, which is why Workbox's route matchers are built around them.
  • When is network-only the right answer for a service worker route?
    Whenever a stored copy could be actively wrong or harmful: authenticated mutations, payment and balance reads, telemetry, and anything whose response is personalised in a way the cache key does not capture. Leaving those un-routed is the simplest implementation — a request the worker does not call `respondWith` on goes to the network as if no worker existed.

saying these in an interview costs you the question

  • Applies one strategy to every request in the worker
  • Calls cache-first correct for HTML because it is faster
  • Thinks stale-while-revalidate returns the fresh response to this request
  • Stores every response including errors and opaque ones
  • Confuses the strategy with the origin's Cache-Control headers

context