skip to content

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%

answer

  1. how does the worker know bytes changed
  2. hashed name versus stable name
  3. null revision means URL is the fingerprint
  4. a precache is a set, not entries
  5. new generation deletes the old wholesale

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.

solid answer

~50 s

A precache manifest lists everything the app needs before it is asked for, and each entry needs a way to answer "is my stored copy still correct?". For content-addressed URLs like `/app.8f3a1c2d.js` the URL itself answers that, so the revision is null. For stable URLs like `/index.html` it cannot, so the build stamps a hash of the contents as the `revision` and the worker compares it against what it stored — changed entries are re-downloaded, unchanged ones are copied forward, which is why a deploy costs kilobytes rather than the whole bundle. The cache name matters for a different reason: a precache is a *set* that must be internally consistent, since the document references specific asset URLs. Writing each build into `precache-v42` and having the new generation delete every cache that is not its own gives you an atomic swap and no orphaned entries, whereas mutating one cache in place leaves a mixture of two builds and grows without bound.

go deeper

for a junior

Know that a precache is a list of URLs downloaded before they are needed, and that the revision field is how the worker tells whether a stable URL's contents changed.

for a middle

Explain the comparison mechanics: null revision for content-addressed URLs, a content hash for stable ones, and why a per-build cache name gives an atomic swap plus a cheap cleanup.

for a senior

Show that you treat the precache as a consistent set — all-or-nothing installs, prefixed cache names so cleanup is safe, and a deliberate line between precached shell and runtime-cached routes.

for a principal

Own the size and cost of the manifest across the fleet: what every user is made to download per deploy, how quota and eviction bound it, and how the build guarantees the manifest matches what shipped.

## Precaching versus runtime caching Runtime strategies react to requests the page makes. Precaching is the opposite: the worker downloads a known list of URLs up front so the app is complete before the user goes offline or navigates. That list — the manifest — is generated by the build, because only the build knows the current filenames. In Workbox this is the `self.__WB_MANIFEST` placeholder that `injectManifest` fills in, and each entry is `{ url, revision }`. ```js const CACHE = 'precache-v42'; const MANIFEST = [ { url: '/app.8f3a1c2d.js', revision: null }, { url: '/index.html', revision: '9d4b1c' }, ]; ``` ## Why the revision field exists The worker has to decide, per entry, whether its stored copy is still the right bytes. There are exactly two ways to know: - **The URL is content-addressed.** `/app.8f3a1c2d.js` changes name whenever its content changes, so a stored entry under that URL can never be wrong. `revision: null` records "the URL is the fingerprint". - **The URL is stable.** `/index.html` and `/manifest.webmanifest` keep their names across builds, so the build hashes the file and ships that hash as `revision`. The worker stores the revision next to the entry; on the next install it compares manifests, and an entry whose revision changed is fetched again while an unchanged one is carried over. Without the revision the worker's only safe option would be to re-download every stable URL on every deploy, or to trust HTTP validators — which is not something you want a precache to depend on, since precaching must produce a known-complete set even when the HTTP cache is cold or aggressive. ## Why the cache name is versioned Cache Storage is a namespace of named caches (`caches.open(name)`), and nothing in the platform expires or garbage-collects an entry. That leaves two possible designs. Mutating one cache in place means that during an update the cache briefly holds some of build 41 and some of build 42, and any request served in that window can mix generations. It also accumulates: entries for assets no longer in any manifest stay forever, quietly consuming the origin's quota. Writing each build into its own cache makes the swap atomic from the page's point of view. The incoming generation fills `precache-v42` while the current one keeps serving from `precache-v41`; when the new generation takes over it deletes every cache that is not its own: ```js caches.keys().then((names) => Promise.all(names.filter((n) => n !== CACHE).map((n) => caches.delete(n))) ); ``` That one-liner is also the reason cache names should be *prefixed and predictable* — a worker must be able to recognise which caches are its own generations and which belong to other features so it does not delete somebody else's runtime cache. ## Two consequences worth mentioning **Precaching is all-or-nothing.** `cache.addAll(urls)` rejects if any single request fails, and nothing is added — which is what you want, since a half-populated precache is a broken app, but it means one 404 in the manifest fails the whole install. Keep the manifest to assets the build actually emits. **Precache what the shell needs, not everything.** Every entry is bytes downloaded by users who may never visit that route, on connections you do not control. The document, the entry bundles and stylesheets, the offline page and the critical fonts and icons is the usual line; route-level chunks and images are better left to runtime strategies.

  • Why is revision null for a URL like /app.8f3a1c2d.js?
    Because the filename already is the fingerprint. A content-addressed URL changes whenever the bytes change, so a stored entry under that URL is correct by construction and there is nothing extra to compare. Stamping a revision on it would add noise and force needless re-downloads whenever the build's hashing scheme changed.
  • What happens if one URL in the precache manifest returns 404 during install?
    `cache.addAll()` rejects and adds nothing, so the install fails and that worker generation never takes over. That is deliberate — a partially populated precache would produce an app that half-works offline — but it makes a manifest listing files the build does not emit a deploy-blocking error.
  • How do you keep the cleanup step from deleting caches that belong to other features?
    Prefix every cache name your worker owns (`myapp-precache-v42`, `myapp-runtime-images`) and filter on that prefix before deleting, rather than deleting everything `caches.keys()` returns. Cache Storage is a flat per-origin namespace shared by anything running on the origin, so an unfiltered sweep is destructive.

saying these in an interview costs you the question

  • Thinks the revision is a timestamp or deploy counter
  • Says Cache Storage entries expire on their own
  • Reuses one cache name and overwrites entries in place
  • Deletes every cache returned by caches.keys() without filtering
  • Precaches the whole build including route chunks and images

context