Skip to content

Cache Storage: the request/response store behind offline PWAs

Published

In one line: Cache Storage is the programmatic, origin-scoped store for Request/Response pairs — the mechanism a service worker uses to serve assets and API responses while offline. You explicitly decide what it holds, unlike the browser’s automatic HTTP cache.

Cache Storage vs the HTTP cache vs IndexedDB

Section titled “Cache Storage vs the HTTP cache vs IndexedDB”
  • The HTTP cache is managed automatically by the browser and is separate from Cache Storage; the Cache API does not honor HTTP caching headers. You don’t directly read or write the HTTP cache.
  • Cache Storage is a store you control directly: you explicitly put, match, and delete whole Response objects, keyed by Request. It is what makes a service worker’s fetch handler able to answer requests with no network.
  • IndexedDB stores structured records and blobs, not HTTP responses. Use it for app data (drafts, queued mutations); use Cache Storage for the app shell and fetched assets.
  • CacheStorage — exposed as caches in windows, workers, and service workers. It is a registry of named Cache objects: caches.open(name), caches.has(name), caches.delete(name), caches.keys(), and a convenience caches.match() that searches across all caches.
  • Cache — a single named store of request→response entries. Key methods:
    • cache.put(request, response) — store a pair you already have.
    • cache.add(request) / cache.addAll(requests) — fetch and store in one step.
    • cache.match(request) / cache.matchAll() — look up a stored response.
    • cache.delete(request) — remove an entry.

A Cache stores Response objects. Because a response body can only be read once, store a clone when you also want to return the response to the page (response.clone()).

caches is available in the window, in Web Workers, and — crucially — in service workers, which is what lets the service worker’s fetch event answer from the cache when the network is unavailable. Pre-store the app shell during install, then serve from the cache in fetch, falling back to (or updating from) the network per your chosen strategy.

const CACHE = 'shell-v3';
const ASSETS = ['/', '/app.css', '/app.js', '/offline.html'];
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(ASSETS)));
});
self.addEventListener('activate', (event) => {
// Delete old caches so a new deploy doesn't serve stale files forever.
event.waitUntil(
caches.keys().then((names) =>
Promise.all(names.filter((n) => n !== CACHE).map((n) => caches.delete(n)))
)
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((hit) => hit || fetch(event.request))
);
});

Cache Storage does not remove your entries on its own under normal use — what you put in stays until you delete it. That makes a deliberate versioning scheme essential:

  • Name caches with a version (shell-v3). Bump the name when assets change.
  • Delete superseded caches in activate, as above, so the previous version’s files are reclaimed instead of accumulating.
  • Be careful with opaque responses. A cross-origin no-cors request yields an opaque response with status 0, which can only be stored with put; cache these only when you must.

Cache Storage data counts toward the origin’s storage usage governed by the Storage API, alongside other web storage such as IndexedDB. Browsers may evict an origin’s storage when quota limits are reached, so treat cached assets as disposable and be ready to re-fetch. Where data must survive eviction, request persistent storage with navigator.storage.persist(). (See the storage persistence reference for the full quota and eviction model.)

The Cache Storage API (CacheStorage and Cache) is supported across current major browsers and is reachable from service workers, making it the baseline offline-asset store for PWAs.

  • Use Cache Storage for request/response assets; use IndexedDB for structured app data.
  • clone() a response before caching if you also return it to the page.
  • Name caches with a version and bump the name when assets change.
  • Delete old caches in the service worker’s activate event.
  • Treat the cache as disposable; design for re-fetch and call persist() when data must survive eviction.
  • Be cautious caching opaque cross-origin responses — you cannot inspect them.