PWA Cache Strategies — Cache-First, Network-First, SWR & Workbox Patterns
Pick cache-first, network-first, or stale-while-revalidate per resource class. Workbox encodes the patterns. You still own versioning and update UX.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
Which strategy fits a fingerprinted app.abc123.js?
Answer
Cache-first. The URL changes when the bytes change, so a hit cannot be an old generation.
L2
Why is cache-first dangerous for index.html?
Answer
HTML points at asset digests. A cached document can reference deleted files or hide a deploy until the cache entry is purged.
L3
How is stale-while-revalidate different from cache-first?
Answer
Both may return a hit immediately. Stale-while-revalidate always starts a network update. Cache-first does not revalidate until a miss or a purge.
L4
What does a network-first timeout buy?
Answer
A slow origin does not hang the navigation. After the budget, the cached shell or a 503 offline response returns.
L5
What should stay network-only?
Answer
Mutations and personalized or secret responses. Offline failure is the correct behavior.
L6
What does Workbox buy in one sentence?
Answer
Routing, strategy classes, and precache revisioning so you do not re-implement Cache Storage footguns. You still choose the strategy.
L7
How do versioned caches and an update toast fit together?
Answer
The shell lives in shell-vN. Activate deletes names outside the allowlist. A waiting worker posts a toast, then skipWaiting and a reload swap generations together.
Failure modes
Cache-first HTML
Users stay on an old shell that references removed hashed assets.
Network-first for every hashed file
Offline breaks, and you pay a network round trip for immutable bytes.
Stale-while-revalidate on a balance
The UI shows a stale number with no version or ETag story, and trust breaks.
Unversioned cache name
Activate has nothing to delete, so runtime entries live until the origin is cleared.
Shared cache for Set-Cookie HTML
A personalized document is reused across users.
Misconceptions
Workbox picks the right strategy for you.
It implements the strategy you register. The resource-class table is still yours.
The HTTP cache and Cache Storage are the same shelf.
The HTTP cache is the browser's. The Cache API is origin storage your worker reads and writes.
Stale-while-revalidate is cache-first with a new name.
The background revalidation is the difference. Cache-first can keep a hit forever.
Interviewer traps
Answering with Redis TTL, stampede locks, or cache-aside.
Those are server caches. This page is the Service Worker Cache API.
One strategy for the whole origin.
Split shell, hashed assets, HTML, lists, and mutations.
index.html just shipped a new asset digest
Prefer
Network-first HTML, cache-first hashes
The document is allowed to update. The fingerprinted file is safe to keep.
- A timeout falls back to the previous shell when the origin is down.
- app.abc123.js can stay cache-first because the URL is the version.
- Activate drops shell-v2 when the allowlist says shell-v3.
Alternative
Cache-first for every GET
The first HTML response wins until something deletes it.
- Offline feels instant, including the bug you just fixed.
- New hashed URLs 404 because the old HTML never learns them.
- Personalized JSON can sit next to the shell.
Classify, then name the cache
- 1
Hashed static asset
Cache-first. The URL is the version. - 2
HTML navigation
Network-first with a timeout, then the cached shell. - 3
List that may be briefly stale
Stale-while-revalidate, and say so in the UI if trust matters. - 4
Mutation or secret
Network-only. Offline failure is correct. - 5
Version the name
shell-vN. Activate deletes every other name.
Overview
Caching strategy is a per-resource-class decision. Hashed static assets love cache-first. HTML navigations usually want network-first. API lists often want stale-while-revalidate. Workbox encodes these patterns. Hand-rolled Cache Storage is fine if you own versioning and update UX.
This is the Cache API inside a Service Worker, not a server-side Redis tier and not the browser HTTP cache. Those layers can sit in front of the same URL and still disagree.
Strategy table
| Strategy | Read path | Write / update | Best for | Failure if misused |
|---|---|---|---|---|
| Cache-first | Hit, else network, then store | On miss or during precache | Fingerprinted JS, CSS, images | Stale unversioned HTML forever |
| Network-first | Network, else cache | Update the cache on success | HTML navigations, critical JSON | Slow offline unless you timeout |
| Stale-while-revalidate | Return the hit now, revalidate in the background | Replace the entry when the network succeeds | Semi-fresh lists, avatars | Users see stale data. They need a version story |
| Network-only | Always network | None | Mutations, auth responses | Offline always fails, which is often correct |
| Cache-only | Always cache | Precache only | App-shell fallbacks | Broken if the precache was incomplete |
What if you choose the other? Cache-first on index.html sticks users on an old shell. Network-first on every /assets/app.abc123.js wastes round trips and breaks offline. Stale-while-revalidate on a bank balance, with no ETag and no copy in the UI, is a trust bug.
Decision path
Flow
- 1
1. Classify the GET
- next2. Hashed asset is cache-first
- 2
2. Hashed asset is cache-first
- next3. HTML navigation is network-first
- 3
3. HTML navigation is network-first
- next4. Stale lists use SWR
- 4
4. Stale lists use SWR
- next5. Mutations stay network-only
- 5
5. Mutations stay network-only
- next6. Fallback page is cache-only
- 6
6. Fallback page is cache-only
- next7. Version the cache name
- 7
7. Version the cache name
Lesson map
PWA Cache Strategies — Cache-First, Network-First, SWR & Workbox Patterns
Pick cache-first, network-first, or stale-while-revalidate per resource class. Workbox encodes the patterns. You still own versioning and update UX.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB a["1. Classify the GET"] b["2. Hashed asset is cache-first"] c["3. HTML navigation is network-first"] d["4. Stale lists use SWR"] a -->|1. Classify the GET| b b -->|2. Hashed asset is cache-first| c c -->|3. HTML navigation is network-first| d
The chain is a checklist, not seven caches. Each request takes one row. The versioned name is the last step for every row that writes.
Hand-rolled shapes
These are the browser shapes. They need Cache Storage and fetch, so they are not the sandbox. The next block runs the same decisions against a Map.
async function cacheFirst(req, cacheName) {
const cache = await caches.open(cacheName);
const hit = await cache.match(req);
if (hit) return hit;
const res = await fetch(req);
if (res.ok) cache.put(req, res.clone());
return res;
}
async function networkFirst(req, cacheName, timeoutMs = 3000) {
const cache = await caches.open(cacheName);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(req, { signal: controller.signal });
clearTimeout(timer);
if (res.ok) cache.put(req, res.clone());
return res;
} catch {
clearTimeout(timer);
return (await cache.match(req)) || new Response("Offline", { status: 503 });
}
}
async function staleWhileRevalidate(req, cacheName) {
const cache = await caches.open(cacheName);
const hit = await cache.match(req);
const refreshing = fetch(req)
.then((res) => {
if (res.ok) cache.put(req, res.clone());
return res;
})
.catch(() => null);
return hit || (await refreshing) || new Response("Offline", { status: 503 });
}Same rules, in memory
Press Run. Snippets must be self-contained — no network, files, or native modules.
Cache-first returns v1 on the second call even though the network offered v2. Stale-while-revalidate shows stale and leaves fresh in the store for the next read.
Workbox versus hand-rolled
| Aspect | Workbox | Hand-rolled Cache API |
|---|---|---|
| Strategy helpers | CacheFirst, NetworkFirst, StaleWhileRevalidate | You write and test each function |
| Precaching | Build manifest via precacheAndRoute | Manual cache.addAll plus revisioning |
| Expiration | ExpirationPlugin | Custom prune on activate |
| Routing | registerRoute matchers | if trees in fetch |
| Bundle cost | A dependency and a build step | No dependency, more footguns |
| Interview signal | Know what Workbox wraps | Prove you understand Cache Storage |
Rule of thumb: use Workbox when the build already emits an asset manifest. Hand-roll a tiny shell or a teaching worker. Either way, version the name (app-shell-v4) and delete old names on activate.
Offline UX and versioned caches
- Keep the app shell in
shell-vNand read it cache-first. - Use separate runtime caches for images and API responses, with a size or age cap.
- On activate, delete every cache not in the allowlist.
- When a worker is waiting, show "Update available", then
skipWaiting, then reload. - Do not put secrets or
Set-Cookiepersonalized HTML in a shared cache unless the key includes the user.
Interview Q&A
Why is cache-first dangerous for HTML?
Answer
HTML is the entry that references asset digests. An old document can point at deleted assets or hide new features until something purges it.
How does stale-while-revalidate differ from cache-first?
Answer
Both may return a cache hit immediately. Stale-while-revalidate always attempts a background network update. Cache-first may never revalidate until a miss or a manual purge.
What does Workbox buy you in one sentence?
Answer
Battle-tested routing, strategy classes, and precache revisioning so you do not re-implement Cache Storage footguns.
When is network-only the right answer?
Answer
Non-idempotent requests and any response that is secret or user-specific and not keyed by that user. Failing offline is the feature.
Why a timeout on network-first navigation?
Answer
A hung origin would otherwise block first paint of a page you already cached. The timeout is the moment you admit the shell.
How do you delete yesterday's shell?
Answer
Put the generation in the cache name. On activate, caches.keys() and delete every name outside the allowlist.
Is the HTTP cache enough for a PWA?
Answer
No. You do not program the HTTP cache per navigation. Cache Storage is the shelf the worker reads. The HTTP cache can still hold sw.js if you are careless with updateViaCache.
What fails if SWR is used for a money balance?
Answer
The user acts on a stale number. You need a network-first read, or an explicit stale badge and a version the server can reject.
Pitfalls
- One
caches.open("app")for shell, API, and avatars. - Caching the opaque redirect that follows a login.
- Assuming Workbox's default route matches your HTML. Read the route you registered.
- Pruning on a timer in the page instead of on activate, so old names survive until the page is open.
Given /, /assets/app.9f3a.js, /api/incidents, /api/incidents/42 as a POST, and /offline.html, name the strategy and the cache name for each. Say which one must not be stored.