Cache API in JavaScript Service Workers for Offline Use

The Cache API provides a persistent storage mechanism in modern browsers that allows service workers to intercept network traffic and save Request/Response object pairs. By decoupling resource delivery from an active internet connection, developers can store critical assets such as HTML, CSS, JavaScript files, and API data directly on the client’s device, enabling web applications to load instantly and function completely offline.

The Request and Response Storage Model

Unlike traditional HTTP caching mechanisms governed by HTTP headers, the Cache API is fully programmable via JavaScript. It acts as a key-value store where the key is a Request object (or a URL string) and the value is a Response object.

Because the storage is tied to the origin of the web application, service workers operate inside an isolated sandbox, ensuring cached network assets are securely segregated per domain.

Opening and Creating Caches

To store data, a service worker first opens a named cache using the global caches.open() method. If the specified cache does not exist, the browser automatically creates it.

const CACHE_NAME = 'app-shell-v1';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles/main.css',
        '/scripts/app.js'
      ]);
    })
  );
});

The cache.addAll() method accepts an array of URLs, fetches them across the network, and automatically saves the resulting Response objects into the cache upon completion.

Dynamic Caching with cache.put()

When handling runtime traffic, service workers intercept fetch events and store responses dynamically using the cache.put() method.

Because Response bodies are readable streams that can only be read once, the response must be cloned using response.clone() before saving it to the cache if it is also being returned to the browser.

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      if (cachedResponse) {
        return cachedResponse;
      }

      return fetch(event.request).then((networkResponse) => {
        if (!networkResponse || networkResponse.status !== 200 || networkResponse.type !== 'basic') {
          return networkResponse;
        }

        const responseToCache = networkResponse.clone();

        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, responseToCache);
        });

        return networkResponse;
      });
    })
  );
});

Retrieving Cached Responses Offline

When a user triggers a network request, the service worker’s fetch event listener intercepts it. The caches.match() method evaluates whether the incoming event.request matches an existing entry in the cache.

If a match is found, caches.match() resolves with the cached Response object without making a network call. If no match is found, the request falls back to the network using fetch(). If the user is offline and the asset has been cached, the application serves the saved response seamlessly.

Cache Maintenance and Cleanup

Because cached data persists indefinitely until manually cleared or purged by the browser under extreme storage pressure, service workers use the activate event to remove outdated caches. By comparing existing cache keys against the current version, obsolete assets are deleted using caches.delete(), ensuring the user does not consume stale resources.