Cache-First and Network-First
Cache-first and network-first
Section titled “Cache-first and network-first”The two most fundamental caching strategies are cache-first and network-first. Cache-first prioritises speed: the service worker serves from cache immediately and only touches the network when there is a cache miss — making it ideal for the app shell, fonts, icons, and other static assets that change infrequently. Network-first prioritises freshness: it always attempts a live fetch and only falls back to the cache when the network is unavailable — making it the right choice for API responses, dynamic HTML pages, and any resource where serving stale data would be harmful.
Choosing between them is a trade-off: cache-first gives instant loads at the risk of stale content; network-first gives fresh content at the cost of a network round-trip on every request.
Cache-first
Section titled “Cache-first”Serve from cache immediately. Only fetch from the network on a cache miss, then cache the result for next time. This strategy is best for static assets that change infrequently — JavaScript bundles, CSS stylesheets, web fonts, and images. Because the cache is checked before the network, pages load at memory speed even on a slow connection.
The key discipline with cache-first is cache versioning: because assets are served from cache without consulting the network, you must bump the cache name (for example from app-v1 to app-v2) whenever you deploy new assets, and delete old caches in the activate event to reclaim storage.
const CACHE = 'app-v1';
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((cached) => { if (cached) return cached; return fetch(event.request).then((response) => { return caches.open(CACHE).then((cache) => { cache.put(event.request, response.clone()); return response; }); }); }) );});flowchart LR
A[Request] --> B{Check cache}
B -- hit --> C[Return cached response]
B -- miss --> D[Fetch from network]
D --> E[Store in cache]
E --> F[Return response] Network-first
Section titled “Network-first”Always try the network first. On network failure, fall back to the cache. This is best for dynamic data where you want freshness — API JSON responses, server-rendered HTML — but still need offline resilience so users are not left staring at a blank page.
The trade-off is that every successful request pays a full network round-trip, so pages load no faster than the server responds. On a slow connection this is noticeably slower than cache-first. Network-first is therefore the wrong choice for large static assets; reserve it for the resources where stale data is genuinely unacceptable.
const CACHE = 'app-v1';
self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request) .then((response) => { return caches.open(CACHE).then((cache) => { cache.put(event.request, response.clone()); return response; }); }) .catch(() => caches.match(event.request)) );});flowchart LR
A[Request] --> B[Fetch from network]
B -- success --> C[Store in cache]
C --> D[Return response]
B -- failure --> E{Check cache}
E -- hit --> F[Return cached response]
E -- miss --> G[Return undefined / error] Try it live
Section titled “Try it live”The demo below registers a service worker that uses cache-first for all requests. Open the StackBlitz project, load the page once so the SW installs, then reload — the second load is served entirely from the cache. Open DevTools → Application → Cache Storage to see the cached entries.