CDNs and edge caching fundamentals
A user in Tokyo requesting a file served from a single origin in Virginia pays for every millisecond of that round trip. A CDN caches copies of your content at edge locations close to users worldwide — so most requests never reach your origin at all. Knowing exactly what gets cached, and how to invalidate it, is the difference between a CDN that helps and one that silently serves stale data.
4 min read
What a CDN actually does
A content delivery network is a globally distributed set of servers (edge locations or points of presence) that sit between users and your origin server. The first request for a resource from a given edge location is a cache miss — the edge fetches it from your origin, serves it, and stores a copy. Every subsequent request for that same resource from nearby users is a cache hit — served directly from the edge, without touching your origin at all.
The win is twofold: lower latency (physically closer server) and lower origin load (most requests never reach it).
What actually gets cached
Not everything is safe to cache. Static assets — JS bundles, CSS, images, fonts — are the obvious case: they're identical for every user and change only on deploy. API responses are trickier: a GET /products/123 response might be safe to cache for a minute if it's the same for everyone, but a GET /users/me response is per-user and must never be cached and served to a different user.
Whether something gets cached, and for how long, is controlled by the Cache-Control HTTP header from your origin:
Cache-Control: public, max-age=31536000, immutable # static asset with a hashed filename — cache forever
Cache-Control: public, max-age=60 # API response, safe to serve stale for up to 60s
Cache-Control: private, no-store # per-user data — never cache at a shared edge
public means shared caches (CDNs) may store it; private means only the end user's own browser may. max-age is the TTL in seconds. Getting this header wrong in either direction is costly: too permissive and you leak one user's data to another; too conservative and you lose the entire benefit of the CDN.
Cache keys: what counts as "the same" request
A CDN decides whether two requests are for the "same" resource using a cache key, typically built from the URL plus a configurable set of headers or query strings. By default, /products?id=123 and /products?id=456 are different cache keys — correct. But if your API returns different content based on an Authorization header or Accept-Language header and that header isn't part of the cache key, the CDN will happily serve one user's response to another. This is one of the most common real-world CDN bugs: forgetting to vary the cache key on something that actually changes the response (Vary: Accept-Language, Vary: Authorization).
Cache invalidation: the hard part
"There are only two hard things in computer science: cache invalidation and naming things" is a cliché because it's true. Once content is cached at dozens of edge locations worldwide, getting a stale copy out of all of them is genuinely hard. Two practical strategies:
- Versioned URLs (cache-busting): name static assets with a content hash (
app.a1b2c3.js), set an effectively infinitemax-age, and change the filename on every deploy. Old cached copies simply become unreferenced — no invalidation needed, because nothing ever points to the stale URL again. - Explicit purge/invalidation: for content whose URL can't change (an
og-image.pngreferenced by a fixed URL, an API response), you actively tell the CDN "this path is now stale" (e.g., a CloudFront invalidation request). This is slower (can take seconds to minutes to propagate globally) and, on some CDNs, costs money per invalidation — so it should be the exception, not the default deploy step.
Why this matters for perceived performance
Time-to-first-byte from a nearby edge is often 10–20x faster than a cross-continent round trip to origin. For a page with dozens of assets, that compounds — every cached asset is one less round trip contending for the user's connection. A CDN misconfigured to bypass cache on static assets (or one that never gets invalidated on dynamic content) throws away most of that benefit in either direction.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does the Cache-Control header 'private, no-store' communicate to a CDN?
2. An API's response varies based on the Accept-Language header, but the CDN's cache key doesn't include it. What happens?
3. Why does the versioned-URL (cache-busting) strategy avoid the need for active cache invalidation?
4. Why is explicit CDN purge/invalidation generally treated as the exception rather than the default deploy step?