Skip to content

Choosing a caching strategy

Published Updated

In one line: the right caching strategy depends on whether the resource is static, dynamic, or time-sensitive — and how tolerant your users are of stale content.

Service workers intercept fetch events and decide where to get the response: the cache, the network, or both. Each strategy makes a different trade-off between speed, freshness, and offline availability.

Check the cache first; fall back to the network if nothing is cached.

async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
return fetch(request);
}

Use for: static assets (JS bundles, CSS, images, fonts) that don’t change between deploys. Once cached, responses are instant — even offline.

Try the network first; fall back to the cache if the request fails or times out.

async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open('dynamic');
cache.put(request, response.clone());
return response;
} catch {
return caches.match(request);
}
}

Use for: dynamic content (API responses, user data) where freshness matters but offline fallback is still valuable.

Return the cached response immediately, then fetch a fresh copy in the background for next time.

async function staleWhileRevalidate(request) {
const cached = await caches.match(request);
const fetchPromise = fetch(request).then((response) => {
const cache = caches.open('dynamic');
cache.then((c) => c.put(request, response.clone()));
return response;
});
return cached || fetchPromise;
}

Use for: semi-static content (UI chrome, article lists) where showing something quickly is more important than showing the absolute latest version.

Always go to the network, never the cache.

Use for: non-GET requests (POST, PUT, DELETE), real-time data (WebSocket connections), or resources that must never be stale.

Serve exclusively from the cache; fail if the resource is not cached.

Use for: pre-cached resources during an install event (app shell, offline fallback page).

Resource type Recommended strategy Why
App shell (HTML, CSS, JS) Cache-first Rarely changes; instant load after first visit
Images and fonts Cache-first Immutable between deploys when hashed filenames are used
API data (user-specific) Network-first Must be fresh; cache provides offline fallback
API data (shared/semi-static) Stale-while-revalidate Show cached data fast, update in background
Navigation requests Network-first with offline fallback Fresh HTML when online; cached shell when offline

For resources that are neither immutable nor critical, add a time check to stale-while- revalidate or cache-first. Store the response timestamp alongside the cached response and fall back to the network if the cache is older than your threshold:

async function cacheWithExpiry(request, maxAgeSeconds) {
const cached = await caches.match(request);
if (cached) {
const cachedDate = new Date(cached.headers.get('date'));
if (Date.now() - cachedDate.getTime() < maxAgeSeconds * 1000) {
return cached;
}
}
return fetch(request);
}
  • Use cache-first for hashed static assets (JS, CSS, images).
  • Use network-first for API data and user-specific content.
  • Use stale-while-revalidate for semi-static lists and UI chrome.
  • Add a precache step during the install event for the app shell.
  • Set a cache name that includes a version string so deploys bust old caches.
  • Limit cache size and expiry to prevent unbounded storage growth.
  • Always provide an offline fallback page for navigation requests.