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.
OptionBenefitCost
Cache-firstFastest possible load, good for static assets that rarely changeRisk of serving stale content if an asset changes but the cache is not invalidated
Network-firstAlways fresh data when the network is availableSlower than cache-first on every request, since it always waits on a network round-trip first
  • Using cache-first for API responses that change frequently, so the user sees stale data without realizing it.
  • Using network-first for large static assets, wasting load time unnecessarily when the asset barely ever changes.
  • Not setting a timeout for network-first, so the user waits on a very slow network instead of falling back to cache sooner.

Twitter Lite’s PWA rebuild used an incremental strategy that mixes cache-first for static assets with fresher, network-touching requests for feed data — matching the split described above (web.dev/twitter). Many news and e-commerce sites use the same split: network-first for time-sensitive content, cache-first for layout and assets, for the same reasons.

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?