Summary
HTTP caching, CDN edge caches, and service workers form a layered system. Each layer intercepts requests before they hit the origin. Getting the directives right — especially distinguishing `max-age` from `s-maxage`, or `no-cache` from `no-store` — determines how fresh users' data is and how little your origin pays per request.
Jump to the interview angleCaching stores a response and reuses it for matching requests. The three main layers are:
- Browser cache — per-user, governed by HTTP
Cache-Controlresponse headers andETag/Last-Modifiedvalidators. - CDN / shared cache — sits between origin and many users; respects
s-maxageandSurrogate-Control. One cached response serves thousands. - App cache (service worker) — JavaScript-controlled, survives offline; you pick the strategy per route.
Cache-Control directives
| Directive | Who it targets | What it does | |
|---|---|---|---|
| max-age=N | Browser | Cache for N seconds; no revalidation within TTL | |
| s-maxage=N | CDN only | CDN TTL; overrides max-age for shared caches | |
| immutable | Browser | Skip revalidation even on hard refresh | |
| no-cache | Browser + CDN | Store, but revalidate before every use | |
| no-store | Browser + CDN | Never store; use for sensitive data | |
| stale-while-revalidate=N | Browser + CDN | Serve stale instantly; refresh in background within N seconds |
Service-worker stale-while-revalidate
Returns the cached response immediately if present, then refreshes in the background. The next request gets the updated copy.
// sw.ts — SWR strategy for API routes
self.addEventListener("fetch", (event: FetchEvent) => {
if (!event.request.url.includes("/api/")) return;
event.respondWith(
caches.open("api-v1").then(async (cache) => {
const cached = await cache.match(event.request);
// Fetch in background — update cache for next request
const networkFetch = fetch(event.request).then((res) => {
if (res.ok) cache.put(event.request, res.clone());
return res;
});
// Serve cached instantly if available; otherwise wait for network
return cached ?? networkFetch;
})
);
});No framework dependency — works in any service worker. Versioning the cache name (api-v1) lets you invalidate old entries when the SW updates.
The golden rule
Content-hashed static assets (e.g., main.abc123.js) get Cache-Control: public, max-age=31536000, immutable. HTML documents get short TTLs or no-store — they are the entry point, and a stale one breaks everything after a deploy.
Interview angle
Interviewers probe the difference between browser and CDN caches and when to use immutable. Strong answers name the three layers, distinguish max-age from s-maxage, explain that content hashing unlocks forever-caching, and mention stale-while-revalidate as the freshness–speed trade.
Soundbite: "Hash asset filenames, cache forever with immutable; keep HTML TTLs short or use CDN purge on deploy."
Key terms
- Cache-Control
- HTTP response header carrying cache directives like `max-age`, `s-maxage`, and `no-store`.
- ETag
- A validator token the server issues; the browser sends it as `If-None-Match` to revalidate without re-downloading the body.
- s-maxage
- CDN-specific TTL that overrides `max-age` for shared caches; the browser ignores it.
- stale-while-revalidate
- Directive (and SW strategy) that serves a stale response instantly, then refreshes in the background.
- immutable
- Tells browsers the resource will never change within `max-age`; suppresses revalidation on hard refresh.