Part III β The Speed Β· Lesson 6.8
Service Worker Caching
In one lineA programmable proxy inside the browser β you write the cache policy in JavaScript.
Where you've seen itA news app that opens and renders yesterday's headlines on a train with no signal, then quietly updates when the tunnel ends.
Diagram
THE INTERCEPT β every request passes through your code
page ββfetch()βββΊ βββββββββββββββββββ
β Service Worker β your JS decides:
β onfetch β
ββββββ¬ββββββββ¬βββββ
β β
ββββββββββββ ββββββββββββ
βΌ βΌ
ββββββββββββββββ ββββββββββββββββ
β Cache Storageβ β Network β
β (your code) β β β
ββββββββββββββββ ββββββββββββββββ
Unlike HTTP caching (6.7), the RULES ARE YOURS β per URL,
per route, per condition.FIVE STRATEGIES β pick per resource type
CACHE FIRST cache βββΊ (miss) network
app shell, fonts, hashed assets fastest, can go stale
NETWORK FIRST network βββΊ (fail) cache
HTML documents, fresh API data correct, slower offline path
STALE-WHILE-REVALIDATE
serve cache NOW, fetch and update in background
avatars, feeds, non-critical data instant + eventually fresh
NETWORK ONLY never cache
payments, auth, analytics correctness over speed
CACHE ONLY precached only
offline fallback pageMAPPING A REAL APP
/ network-first (fresh content, offline fallback)
/assets/*.js|css cache-first (hashed, immutable)
/fonts/*.woff2 cache-first (never change)
/api/feed SWR (instant, refreshes behind)
/api/checkout network-only (must never be stale)
/offline.html precached (the fallback)How it works
- Precache the shell at install: the HTML fallback, CSS, JS, fonts, and logo.
- Route requests by type inside
fetch, applying the strategy that matches the resource's freshness needs. - Version your caches (
static-v4) and delete old ones inactivate, or storage grows without bound. - Cap runtime caches β max entries and max age β because unbounded image caching will fill the quota.
- Use Workbox in production; hand-rolled service workers are where subtle staleness bugs live. Lifecycle details are covered in 9.1.
self.addEventListener('fetch', (event) => {
const { request } = event;
if (request.mode === 'navigate') {
event.respondWith(
fetch(request).catch(() => caches.match('/offline.html')) // network-first
);
} else if (request.url.includes('/assets/')) {
event.respondWith(
caches.match(request).then((hit) => hit ?? fetch(request)) // cache-first
);
}
});Trade-offs
| Service worker cache | HTTP cache |
|---|---|
| You write the logic | Browser applies headers |
| Works fully offline | Needs the network for revalidation |
| Per-route strategies | Per-response headers |
| A bug ships to every user's device | Header change takes effect immediately |
The danger: a broken service worker is sticky β it can keep serving a broken app until it's updated. Always ship a kill switch (an SW that unregisters itself) and test the update path, not just the install path.
Interview angle
"How would you make a news site work offline?"
- Precache the shell, network-first for documents with an offline fallback, cache-first for hashed assets.
- Stale-while-revalidate for the feed so it opens instantly and refreshes behind the user.
- Version the caches, cap the image cache, and keep payments network-only β never serve a stale transaction.
Recap
- The service worker is a proxy you program; HTTP caching is a policy you declare.
- Match strategy to resource: shell cache-first, documents network-first, feeds SWR.
- Version and bound your caches, and always have an escape hatch.