Caching strategies for service workers
In one line: A caching strategy is the rule your service worker follows when a fetch event fires — whether to go to the cache, the network, or both, and in what order. The five standard strategies cover every offline and performance trade-off a PWA needs.
The five strategies
Section titled “The five strategies”Cache First (cache falling back to network)
Section titled “Cache First (cache falling back to network)”Check the cache first. On a hit, return the cached response. On a miss, fetch from the network and optionally store the result.
Best for: Static assets with long-lived content (app shell, fonts, images, versioned JS bundles). Cache hits are instant; misses pay the full network round-trip.
Avoid for: Any resource that must always be fresh (user data, prices, auth tokens).
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((cached) => { if (cached) return cached; return fetch(event.request).then((response) => { const clone = response.clone(); caches.open('v1').then((cache) => cache.put(event.request, clone)); return response; }); }) );});Network First (network falling back to cache)
Section titled “Network First (network falling back to cache)”Try the network. On success, cache and return the fresh response. On network failure, fall back to whatever is cached.
Best for: Pages that should be current but tolerate a stale offline fallback (news feeds, dashboards).
Avoid for: Large, infrequently changing assets — every request pays the network penalty.
self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request) .then((response) => { const clone = response.clone(); caches.open('v1').then((cache) => cache.put(event.request, clone)); return response; }) .catch(() => caches.match(event.request)) );});Stale-While-Revalidate
Section titled “Stale-While-Revalidate”Return the cached version immediately (fast), then fetch a fresh copy in the background and update the cache for next time.
Best for: Resources where speed matters more than absolute freshness (avatar images, non-critical API data, secondary scripts). The user always sees something fast; the background fetch keeps the cache warm.
Avoid for: Resources where the stale copy can cause errors if loaded alongside fresher content in the same page load.
self.addEventListener('fetch', (event) => { event.respondWith( caches.open('v1').then((cache) => cache.match(event.request).then((cached) => { const networkFetch = fetch(event.request).then((response) => { cache.put(event.request, response.clone()); return response; }); return cached ?? networkFetch; }) ) );});Network Only
Section titled “Network Only”Always go to the network. The service worker does not cache anything.
Best for: Requests that must never be served stale: analytics pings, POST mutations, payment requests. Effectively a passthrough.
Cache Only
Section titled “Cache Only”Only serve from the cache. If nothing is cached, the request fails.
Best for: Resources that were pre-cached during install and are guaranteed to exist (the offline shell). Never use for dynamic or user-specific content.
Choosing a strategy
Section titled “Choosing a strategy”| Resource type | Recommended strategy |
|---|---|
| App shell (HTML, CSS, core JS) | Cache First with version-keyed cache names |
Versioned static assets (/assets/main.abc123.js) |
Cache First — the URL itself changes on deploy |
| API responses, pages that should be current | Network First with offline fallback |
| Avatars, thumbnail images, secondary fonts | Stale-While-Revalidate |
| Auth, payments, analytics | Network Only |
| Offline fallback page | Cache Only (pre-cached in install) |
Using Workbox
Section titled “Using Workbox”Workbox implements all five strategies as composable classes, removing the need for hand-rolled fetch event handlers:
import { registerRoute } from 'workbox-routing';import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// App shell — cache first, only cache 200 responsesregisterRoute( ({ request }) => request.mode === 'navigate', new NetworkFirst({ cacheName: 'pages' }));
// Images — stale-while-revalidateregisterRoute( ({ request }) => request.destination === 'image', new StaleWhileRevalidate({ cacheName: 'images' }));
// Versioned assets — cache first, long-livedregisterRoute( ({ url }) => url.pathname.startsWith('/assets/'), new CacheFirst({ cacheName: 'static-assets', plugins: [new CacheableResponsePlugin({ statuses: [200] })], }));See Workbox for full setup details.
Practical checklist
Section titled “Practical checklist”- Choose a strategy per resource type, not one strategy for everything.
- Version cache names (e.g.
shell-v3) and delete old caches inactivate. - Never cache non-idempotent requests (POST, PUT, DELETE) unless you’re intentionally building a background sync queue.
- For Cache First assets, tie the URL to the content hash so stale content is never served from cache after a deploy.
- Add an explicit offline fallback page so Network First misses produce a usable response, not a browser error. See Offline fallback.
Cross-references
Section titled “Cross-references”- The Cache API —
caches.open(),cache.put(), andcache.match()primitives - The Fetch event and routing — how
fetchevents reach the service worker - Workbox — production-ready strategy implementations
- Offline fallback — serving a meaningful offline page