Understanding Chrome’s Caching System
A practical guide to how Chrome stores, validates, and serves cached responses — and how you can control it for performance and reliability.
Why caching matters
Latency, bandwidth, resilience, and user experience gains from not hitting the network unnecessarily.
Browser caching in Chrome stores copies of network resources to improve performance, reduce latency, and decrease bandwidth usage. It enables offline support and smoother UX by serving assets locally instead of redownloading them.
- Performance: Faster page loads and less jank once core assets are cached.
- Cost: Reduced bandwidth on both client and server.
- Reliability: Pages can keep working even on flaky or offline connections.
HTTP caching basics
Control freshness, storage, and who is allowed to cache with response headers.
| Header | Effect |
|---|---|
Cache-Control: max-age=SECONDS |
Specifies how long a resource remains fresh in cache before it must be revalidated. |
Cache-Control: no-cache |
Requires validation with the server before reuse even if stored. |
Cache-Control: no-store |
Instructs the browser not to store the response at all. |
Cache-Control: public / private |
Determines if responses can be cached by intermediaries or only locally. |
Types of caches in Chrome
Multiple layers cooperate: memory, disk, page snapshots, and programmable caches.
- Memory cache: Fastest, holds recently used assets for quick reuse while the tab lives.
- Disk HTTP cache: Stores resources on disk based on cache headers and eviction policies.
- Back/Forward cache: Keeps entire page state for instant back/forward navigation.
- Service worker cache: Cache Storage API lets you programmatically store and serve responses.
Chrome DevTools & cache inspection
See exactly what was cached, when, and why it was (or wasn’t) reused.
Open DevTools with Ctrl+Shift+I (Windows/Linux) or Cmd+Opt+I (Mac). Key areas:
- Network panel: View cache hits/misses, status codes, and response headers.
- Application → Cache Storage: Inspect service worker caches, entries, and payloads.
- Reload menu: Right‑click the reload icon to use “Empty Cache and Hard Reload” when testing.
Programmatic caching with service workers
Install‑time precaching plus fetch‑time routing give you full control over responses.
// Register service worker
navigator.serviceWorker.register('/sw.js');
// Inside sw.js
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open('static-v1').then(cache => cache.addAll([
'/', '/style.css', '/app.js'
]))
);
});
self.addEventListener('fetch', evt => {
evt.respondWith(
caches.match(evt.request).then(resp => resp || fetch(evt.request))
);
});
This pattern precaches core assets during install so subsequent requests can be served instantly from cache, falling back to the network when needed.
Chrome URLs for cache debugging
Built‑in diagnostic pages that reveal what Chrome is storing under the hood.
| chrome:// URL | Purpose |
|---|---|
| chrome://cache | Displays cache index (list of cached resources). |
| chrome://net-internals | Advanced network and cache diagnostics. |
| chrome://serviceworker-internals | Lists registered service workers and cache details. |
Cache invalidation strategies
How to ship new code without serving stale files forever.
Cache invalidation ensures users receive updated content when files change. Common patterns:
-
Filename versioning (recommended):
app.v1.js → app.v2.js
New filenames mean the browser treats the asset as fresh without needing heuristics. -
Query string versioning:
/app.js?v=123
Works, but some CDNs and proxies treat query strings differently from path changes. -
Cache-Control revalidation:
Cache-Control: must-revalidate
Forces the browser to check with the origin when a resource is stale.
How Chrome decides what to cache
A high‑level decision tree for each request.
Request
↓
Is resource cacheable?
↓ (no-store, private data, etc.)
Yes
↓
Is it still fresh? (max-age / Expires)
↓
Fresh Stale
↓ ↓
Serve from Revalidate with ETag /
cache Last-Modified
↓
304 Not Modified? ── Yes ─→ Serve cached copy
│
No
↓
Fetch from network → Update cache → Serve new response
ETag & Last‑Modified examples
Conditional requests let Chrome reuse bytes without refetching everything.
ETag example
ETag: "abc123"
Next request from the browser:
If-None-Match: "abc123"
If unchanged, the server returns:
304 Not Modified
Last‑Modified example
Last-Modified: Tue, 05 Jan 2026 10:00:00 GMT
Browser sends:
If-Modified-Since: Tue, 05 Jan 2026 10:00:00 GMT
Cache Storage vs IndexedDB vs LocalStorage
Picking the right storage layer for assets, structured data, and small flags.
| Storage type | Best for | Limits |
|---|---|---|
| Cache Storage API | Storing HTTP responses and offline assets. | Opaque responses count strongly toward origin quota. |
| IndexedDB | Structured data, large datasets, search indexes. | Async API and more complex data modeling. |
| LocalStorage | Small key/value flags and user prefs. | Small quota (~MBs), synchronous (blocks main thread). |
Advanced service worker caching patterns
Choose a strategy per route or asset type instead of “one size fits all”.
Cache‑first (great for static assets):
evt.respondWith(
caches.match(evt.request).then(cached => cached || fetch(evt.request))
);
Network‑first (for APIs that must be fresh):
evt.respondWith(
fetch(evt.request).catch(() => caches.match(evt.request))
);
Stale‑while‑revalidate (for UX + freshness):
evt.respondWith(
caches.match(evt.request).then(cached => {
const fetchPromise = fetch(evt.request).then(resp => {
caches.open('dynamic').then(cache =>
cache.put(evt.request, resp.clone())
);
return resp;
});
return cached || fetchPromise;
})
);
Service worker request flow
How a fetch travels through your service worker’s routing logic.
Browser request
↓
Service worker intercepts
↓
Is request in cache?
├─ Yes → Return cached response
│
└─ No → Fetch from network
↓
Optionally save to cache
↓
Return network response
Real‑world caching examples
Copy‑paste header patterns for common scenarios.
Static asset caching (versioned bundle):
Cache-Control: public, max-age=31536000, immutable
API responses (validate before reuse):
Cache-Control: no-cache
Sensitive data (never stored):
Cache-Control: no-store
Chrome cache quotas & limits
Origin‑scoped storage budgets and what happens when you exceed them.
- Chrome allocates a fraction of disk space per origin (exact numbers vary by platform).
- Opaque responses (e.g. cross‑origin without CORS) count more heavily toward quota.
- Eviction generally uses “least recently used” style heuristics when space is needed.
Debugging cache issues (checklist)
Systematically rule out which cache layer is serving stale content.
- Enable Network → Disable cache in DevTools to bypass the HTTP cache during testing.
- Verify response headers for
Cache-Control,ETag, andLast-Modified. - Check the Application → Service Workers panel for old workers still controlling the page.
- Use
chrome://cacheor DevTools Application panels to confirm what’s stored. - Compare “Hard reload”, “Empty cache and hard reload”, and normal reload behavior.
Comments
Post a Comment