skip to content

Service Worker Caching Strategies

You will learn the named strategies teams actually deploy and which asset class each one suits. Interviewers ask you to pick a strategy per asset type — HTML, hashed bundles, API JSON — and justify it.

on this pageshow

questions

6

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

open as a page

A team ships a service worker that answers every request cache-first, including the HTML document at /. After the next deploy, users keep loading the old app even after reloading the page. Explain why, and how you would change the caching strategy.

level: seniorimportance: must knowfreq 62%

basics

~20 s

The document's URL never changes, so cache-first keeps answering it from the stored copy and never sees the new deploy. That old HTML also references the old bundle hashes, pinning the whole app. Fix it by serving navigations network-first and reserving cache-first for hashed URLs.

open as a page

In a service worker, what does the stale-while-revalidate caching strategy do when a request comes in, and what does that mean for what the user actually sees on screen?

level: juniorimportance: should knowfreq 52%

basics

~20 s

It answers the request from the cache straight away and, in parallel, fetches from the network and stores the fresh copy. The user sees the previous version instantly; the new data appears only on the next request, unless the app re-renders when the update lands.

open as a page

A build tool generates a service worker precache manifest whose entries look like { url, revision }. Why does the manifest carry a revision alongside the URL, and why is each deploy's precache given a new cache name rather than overwriting entries in one long-lived cache?

level: middleimportance: should knowfreq 45%

basics

~20 s

The revision is a content fingerprint that tells the worker an unhashed URL's bytes changed, so it re-downloads only what differs. A per-build cache name keeps each deploy's asset set intact and self-consistent, and lets the new generation delete the previous one wholesale instead of leaving orphans.

open as a page

A service worker handles navigation requests network-first and falls back to a cached /offline.html. On a cold visit the page feels slower than it did before the worker existed. What causes that delay, and what does navigation preload do about it?

level: seniorimportance: should knowfreq 33%

basics

~20 s

When the worker is not already running, the browser must boot it and evaluate its script before the fetch handler can even start the network request, so startup time is added in front of the document. Navigation preload makes the browser issue that request in parallel with worker startup.

open as a page

Your team wants a service worker to cache authenticated API responses in Cache Storage so the app works offline. How would you decide whether that is acceptable, and what would you require before approving it?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Weigh the offline benefit against storing per-user data on disk under an origin-wide, unencrypted namespace keyed by URL. Approve it only for read data whose staleness is harmless, with per-user cache names, a hard clear on logout and account switch, and a bounded, audited entry set.

open as a page