Cache First Strategy in JavaScript Service Workers
This article provides an overview of the Cache First (also known as Cache Falling Back to Network) caching strategy and demonstrates how to implement it using vanilla JavaScript Service Workers. You will learn the mechanics behind this strategy, ideal use cases for modern web applications, and complete code examples for intercepting requests and caching assets effectively.
What is the Cache First Strategy?
The Cache First strategy is a caching pattern where the Service Worker intercepts a network request and immediately checks the cache to see if the requested resource is already stored. If a matching response is found in the cache, the Service Worker returns it directly to the browser without making a network call.
If the resource is not present in the cache, the Service Worker falls back to fetching it from the network. Once the network request succeeds, the response is typically stored in the cache for future requests before being returned to the client.
Request -> Service Worker -> Match in Cache?
├── Yes -> Return Cached Response
└── No -> Fetch from Network -> Store in Cache -> Return Response
When to Use Cache First
This strategy is ideal for non-critical, static, or immutable assets that do not change frequently, such as:
- Static images, icons, and SVG files
- Custom web fonts (e.g., WOFF2 files)
- Versioned or hashed CSS and JavaScript bundles (e.g.,
app.a1b2c3.js)
Implementing Cache First in a Service Worker
Implementing the Cache First strategy requires handling three core
Service Worker lifecycle events: install,
activate, and fetch.
1. Pre-caching Core Assets on Install
During the install event, create a named cache and
pre-cache essential static assets.
const CACHE_NAME = 'static-assets-v1';
const PRECACHE_ASSETS = [
'/',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.svg'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(PRECACHE_ASSETS);
})
);
self.skipWaiting();
});2. Intercepting Requests with the Fetch Handler
Inside the fetch event listener, implement the Cache
First logic. Check the cache using caches.match(). If the
asset is missing, retrieve it via fetch(), clone the
response, and store it in the cache for subsequent visits.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
// 1. Return cached response if found
if (cachedResponse) {
return cachedResponse;
}
// 2. Fall back to network if not in cache
return fetch(event.request)
.then((networkResponse) => {
// Check if response is valid
if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') {
return networkResponse;
}
// Clone the response because it is a single-use stream
const responseToCache = networkResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return networkResponse;
})
.catch(() => {
// Optional: Return a custom offline fallback asset if network fails
});
})
);
});3. Cleaning Up Outdated Caches on Activation
Use the activate event to remove outdated caches
whenever you deploy a new version of your Service Worker.
self.addEventListener('activate', (event) => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});Summary
The Cache First strategy provides near-instant load times and offline capabilities by prioritizing locally stored assets over network calls. It is best applied to static, immutable files where immediate delivery outweighs the need for real-time updates.