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:

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.