Lesson 0003 · ~12 minutes
One skill: pair Cache Storage with a service worker and pick a caching strategy that matches freshness vs offline needs.
In a design review you can say: “warehouse vs policy,” name install/activate jobs, and pick cache-first vs network-first vs stale-while-revalidate for a given resource — without building a full PWA checklist yet.
Cache Storage stores Request/Response pairs. It does nothing automatic on page load.
A
service worker
can intercept fetch and call
event.respondWith(...). That policy is the
strategy. Jake
Archibald’s
Offline Cookbook
is the classic catalog: caching and serving are separate knobs — use
them in tandem by URL and context.
High-level browser order when a resource is requested (SW caching and HTTP caching):
fetch)
Cache Storage = where HTTP-shaped resources live. The service worker = when and how they are filled and served. Different routes can use different strategies. Workbox is optional sugar later — the decisions are the same.
From the Offline Cookbook’s “when to store” map (not every product needs all of these):
Precache versioned shell assets with
event.waitUntil(caches.open(...).then(c => c.addAll(...))).
If addAll fails, install fails — good for must-have
deps.
Delete old named caches once the previous SW is gone. Keep activation lean — long activate queues fetch and blocks loads.
On network response: if no cache hit, fetch, return, and
put a clone for next time. Watch storage bloat (images,
unbounded URLs).
“Save offline” from the page: Cache API works in window scope too —
open a named cache and addAll what the user chose.
// install: must-have static for this SW version
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open("static-v3").then((cache) =>
cache.addAll(["/index.html", "/app.js", "/app.css"])
)
);
});
// activate: drop previous generations (careful: origin-wide names)
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names
.filter((n) => n.startsWith("static-") && n !== "static-v3")
.map((n) => caches.delete(n))
)
)
);
});
Names vary slightly across docs; meanings below match the Offline Cookbook and the strategy table on web.dev.
| Strategy | Idea | Classic fits |
|---|---|---|
| Cache, falling back to network | match → else fetch | Offline-first shell; most static for a versioned build |
| Network, falling back to cache | fetch → on fail, match | Prefer fresh (status, prices with caveats); stale if offline |
| Stale-while-revalidate |
Return cache if any; also fetch and put for next
time
|
Avatars, product lists, “fast now, fresher later” |
| Network only | Always network (or skip respondWith) |
Payments, analytics pings, non-GET |
| Cache only | Only match (miss ≈ error) | Precached assets you know exist for this version |
// Cache, falling back to network
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request);
})
);
});
// Network, falling back to cache
self.addEventListener("fetch", (event) => {
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
});
// Stale-while-revalidate (core idea)
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.open("dyn-v1").then(async (cache) => {
const cached = await cache.match(event.request);
const networkPromise = fetch(event.request).then((res) => {
cache.put(event.request, res.clone());
return res;
});
return cached || networkPromise;
})
);
});
When the SW goes to the network, the browser may still serve a long
max-age from the
HTTP cache. Strategy + header design should
cooperate; SW TTL and HTTP TTL need not be identical — web.dev often
suggests longer SW-side control when you want offline reliability
(same article). Deep header recipes can wait; know the two layers exist.
Equal-length options. Immediate feedback.
Fingerprinted /app.abc123.js and CSS that must load
offline and instantly on repeat visits.
Checkout total and payment intent must never show a stale cached amount as if it were live.
Order status: prefer fresh when online; if the network fails, last cached status is acceptable with a disclaimer.
User avatars: show something immediately; latest image on a later visit is fine.
You shipped static-v4. Where do you usually delete
static-v3 named caches?
fetch).
put carefully.
web.dev — Offline Cookbook (Jake Archibald). Best primary for “when to store” + serving patterns. Optional second: Service worker caching and HTTP caching for the two-layer model.