Skip to content

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.

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]
Cache-first strategy

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]
Network-first strategy

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.

Runs a real service worker + manifest in your browser.
Which strategy tries the network first and only falls back to cache on failure?
Cache-first is most appropriate for:
How do you invalidate stale cache-first assets after a deploy?
What does the network-first fetch handler return when both the network and cache fail?