CDN & Browser Caching¶
What is it: A CDN (Content Delivery Network) is a geographically distributed network of edge servers that cache content close to users. Browser caching stores responses locally in the browser's cache, serving them without any network request.
Why: Latency from Vietnam to a US origin server = 150–200ms RTT. A CDN edge server in Singapore = 10–20ms RTT. For static assets (images, JS, CSS), serving from a nearby edge reduces latency by 10×. Browser caching eliminates latency entirely for unchanged resources — no network at all.
Cache-Control header (server → client):
- max-age=3600: cache this response for 3600 seconds. After expiry, re-fetch.
- no-cache: always revalidate with the server before using the cached version (send conditional request).
- no-store: never cache — every request fetches fresh (sensitive data).
- s-maxage=600: CDN cache duration (overrides max-age for shared caches, not browsers).
- private: only the browser may cache, not CDN (user-specific data like profile pages).
- public: CDN may cache this response.
- immutable: browser should never revalidate during max-age (for content-hashed assets).
ETag & conditional requests:
The server sends ETag: "abc123" (a fingerprint of the resource). On re-fetch after expiry, the browser sends If-None-Match: "abc123". If unchanged, the server returns 304 Not Modified (no body — saves bandwidth). If changed, the server returns 200 with the new body and a new ETag.
Last-Modified / If-Modified-Since: An alternative to ETag using timestamps. Less reliable than ETags (timestamp granularity, clock skew).
Why the second request is faster:
- If max-age hasn't expired: served from browser cache with zero network I/O (~0ms).
- If expired but resource unchanged: server returns 304 Not Modified — only headers, no body. ~RTT with no bandwidth cost.
- If resource changed: full response from CDN edge (nearby, low latency) instead of origin.
CDN cache invalidation:
CDN edges respect Cache-Control TTLs. For immediate invalidation, use: versioned URLs — app.v2.js is a new URL, so the old app.v1.js can be cached indefinitely (cache-control: immutable); or purge via CDN API (Cloudflare, CloudFront purge API) to evict specific keys from all edge nodes.
Trade-offs:
Long TTLs: fewer origin requests, better performance, but stale content until expiry. Short TTLs: always fresh, more origin requests, higher origin load. The solution: content-hash the filename for static assets (Webpack/Vite does this automatically) — the URL changes only when content changes, so you can use max-age=31536000, immutable safely.
Common pitfall¶
Setting a long max-age on an HTML file that references
content-hashed assets by a non-hashed filename (main.js instead of
main.a1b2c3.js) breaks the entire strategy — the HTML itself now has
to stay fresh (short or no cache) for its asset references to ever
update, while the assets it points to can safely cache forever. Mixing
this up — caching the HTML aggressively and the assets conservatively,
the reverse of what's needed — is a common way "we already do
content-hashed caching" still ships stale pages to users.