Cache Storage API
Precache and runtime cache with the Cache API: match, put, strategies, and how it differs from HTTP cache.
- 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
response.clone()before reading body if you also put in cache — body streams once.- Opaque responses (no-cors) are cacheable but unreadable — size quotas surprise you.
- POST/PUT generally should not be cache-first.
- Forgetting to delete old caches leaks quota.
- 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.
Related
Further reading
Related guides
- Service Workers OverviewSW lifecycle install/activate/fetch, scope, update story, and what belongs in a service worker vs the page.
- BFCache Back Forward CacheHow the back/forward cache freezes pages for instant history nav, what blocks it, and how to restore state safely.
- Browser DevTools Network PanelRead waterfalls, timing phases, headers, throttling, and initiator chains in the Network panel like a production debugger.
- Browser DevTools Performance PanelRecord main-thread timelines: long tasks, style/layout/paint, frames, and how to turn flame charts into INP fixes.
- Browser Extension Messaging BasicsHow content scripts, background/service workers, and pages talk: runtime messaging, ports, and security boundaries.