ESC

Type to search the knowledge base.

Cache Storage API

Precache and runtime cache with the Cache API: match, put, strategies, and how it differs from HTTP cache.

advanced3 min read
  • browser
  • cache-storage
  • service-worker
  • offline

The Cache Storage API (caches) lets you store complete HTTP Response objects keyed by Request, primarily from service workers. It is not localStorage for strings and not the browser’s automatic HTTP disk cache — it is an explicit, origin-scoped cache you control.

Docs: MDN Cache, Service Worker Caching strategies.

Mental model

// open named cache → match or put Request/Response pairs
const cache = await caches.open('app-shell-v3');
await cache.put('/offline.html', response);
const hit = await cache.match('/offline.html');

Caches are versioned by name in practice: when you ship a new SW, open app-shell-v4, precache, then delete old names in activate.

Precaching the app shell

const PRECACHE = 'precache-v12';
const ASSETS = ['/', '/index.html', '/app.abc123.js', '/app.abc123.css'];

self.addEventListener('install', (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(PRECACHE);
      await cache.addAll(ASSETS);
      self.skipWaiting();
    })(),
  );
});

Use fingerprinted URLs for assets. Precaching /app.js without a hash makes updates painful.

Runtime strategies (sketch)

self.addEventListener('fetch', (event) => {
  const { request } = event;
  if (request.method !== 'GET') return;

  event.respondWith(
    (async () => {
      const cache = await caches.open('runtime-v1');
      const cached = await cache.match(request);
      if (cached) return cached; // cache-first (good for immutable static)

      const response = await fetch(request);
      if (response.ok) await cache.put(request, response.clone());
      return response;
    })(),
  );
});
Strategy Behavior Good for
Cache first Cache hit → else network Hashed static assets
Network first Network → else cache HTML shell, APIs (with care)
Stale-while-revalidate Return cache, update in bg Semi-fresh content

Workbox encodes these; understand them before copy-pasting.

Cache vs HTTP cache

HTTP cache Cache Storage
Control Headers (Cache-Control) Your SW code
Keys URL + vary rules Request match options
Offline Not guaranteed Explicit offline UX
Opaque responses N/A Cross-origin no-cors caveats

You often use both: long-lived HTTP cache for CDNs, Cache API for offline shells.

Footguns

  1. response.clone() before reading body if you also put in cache — body streams once.
  2. Opaque responses (no-cors) are cacheable but unreadable — size quotas surprise you.
  3. POST/PUT generally should not be cache-first.
  4. Forgetting to delete old caches leaks quota.
  5. Caching personalized API responses without auth keying leaks data across users on shared machines (rare SW bug class: wrong cache keys).
self.addEventListener('activate', (event) => {
  event.waitUntil(
    (async () => {
      const keys = await caches.keys();
      await Promise.all(
        keys.filter((k) => k !== PRECACHE).map((k) => caches.delete(k)),
      );
      await self.clients.claim();
    })(),
  );
});

Interview out-loud

“Cache Storage holds Request/Response pairs under named caches, driven by the service worker. Precache fingerprinted assets, pick cache-first vs network-first deliberately, clone responses when caching, and version/delete old caches on activate.”

Offline UX expectations

Caching GET shells is not the same as offline-first data. Queue POSTs carefully (Background Sync / periodic sync where available, or custom replay with idempotency keys). Show explicit “you’re offline” UI so users don’t think a button is broken when fetch fails.

Version caches by build ID (precache-2026-08-04-a1b2) rather than only semver of the app — multiple deploys per day need uniqueness.

Further reading

Related guides