patternjavascriptMajor
Cache strategies — cache-first, network-first, stale-while-revalidate
Viewed 0 times
Cache API, Service Worker API
cache-firstnetwork-firststale-while-revalidateservice worker cachePWA caching strategies
browser
Problem
Using a single cache strategy for all resources produces either stale data (cache-first on API calls) or poor offline experience (network-first on static assets). Developers apply one strategy universally and hit tradeoffs.
Solution
Match cache strategy to resource type:
// Cache-first (static assets: JS, CSS, images, fonts)
// Fast, offline-capable. Risk: stale assets until cache version bumps.
self.addEventListener('fetch', event => {
if (isStaticAsset(event.request.url)) {
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
}
});
// Network-first (API calls, HTML documents)
// Fresh data when online. Falls back to cache when offline.
function networkFirst(request) {
return fetch(request)
.then(res => { / clone and cache / return res; })
.catch(() => caches.match(request));
}
// Stale-while-revalidate (frequently updated but not critical: avatars, non-breaking API)
// Returns cache instantly, then updates cache in background.
function staleWhileRevalidate(request) {
return caches.open('dynamic').then(cache =>
cache.match(request).then(cached => {
const fetchPromise = fetch(request).then(res => {
cache.put(request, res.clone());
return res;
});
return cached || fetchPromise;
})
);
}
// Cache-first (static assets: JS, CSS, images, fonts)
// Fast, offline-capable. Risk: stale assets until cache version bumps.
self.addEventListener('fetch', event => {
if (isStaticAsset(event.request.url)) {
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
}
});
// Network-first (API calls, HTML documents)
// Fresh data when online. Falls back to cache when offline.
function networkFirst(request) {
return fetch(request)
.then(res => { / clone and cache / return res; })
.catch(() => caches.match(request));
}
// Stale-while-revalidate (frequently updated but not critical: avatars, non-breaking API)
// Returns cache instantly, then updates cache in background.
function staleWhileRevalidate(request) {
return caches.open('dynamic').then(cache =>
cache.match(request).then(cached => {
const fetchPromise = fetch(request).then(res => {
cache.put(request, res.clone());
return res;
});
return cached || fetchPromise;
})
);
}
Why
There is no universally optimal cache strategy. Static assets can tolerate cache-first with versioned filenames; real-time API data requires network-first; content that benefits from perceived speed but tolerates brief staleness suits stale-while-revalidate.
Gotchas
- Never cache POST requests or API endpoints that mutate data
- Cache-first without a versioned cache key means users receive old assets indefinitely after a deployment
- Stale-while-revalidate can cause layout shifts if the cached and fresh versions differ structurally
- Opaque responses (cross-origin without CORS) can consume disproportionate quota in some browsers — avoid caching them
Context
Designing service worker fetch handler for a PWA with mixed resource types
Revisions (0)
No revisions yet.