Content Delivery Networks
Date: 2026-08-17
A geographically distributed cache that answers from near the user instead of from your origin. It buys latency, resilience and absorbed traffic — and hands you the hardest problem in computing in exchange, which is knowing when a copy is stale.
A CDN (content delivery network) is a network of servers in many locations, each holding copies of your content and serving whichever users are nearest.
WITHOUT WITH
user (Sydney) user (Sydney)
│ 16,000 km │ 20 km
▼ ▼
origin (London) edge (Sydney)
RTT ~280ms RTT ~5ms
│ only on a miss
▼
origin (London)
The primary gain is latency, not bandwidth — the bytes travel less far, so every round trip is shorter — Latency and Bandwidth.
What it does beyond caching
- Absorbs traffic. A campaign spike hits the edge, not your server
- DDoS mitigation, because there is a large distributed network in front of you
- TLS termination at the edge, so the handshake is local and fast — TLS and HTTPS
- Compression, image transformation, format negotiation — WebP or AVIF per client, resized per device
- Edge compute. Small functions running near the user — redirects, A/B assignment, personalisation, header rewriting — Edge Computing
- A stable front door, so origin changes don’t move the DNS record
Cache keys, and the thing that ruins hit rates
The cache key determines what counts as “the same response”. By default it’s usually the URL — and everything you add to it multiplies the number of stored copies.
GOOD HIT RATE
key = URL
DESTROYED HIT RATE
key = URL + all query params
?utm_source=... creates a distinct
entry per campaign, per variant,
per click
key = URL + cookie
a session cookie means a cache entry
per user — which is no cache at all
Strip marketing parameters from the cache key. utm_*, gclid, fbclid and their kin don’t change the response, and leaving them in is one of the most common causes of a CDN that appears to be doing nothing.
Vary behaves the same way. Vary: User-Agent forks the cache per browser string — HTTP Semantics.
Static versus dynamic
STATIC images, CSS, JS, fonts
immutable, hashed filenames
→ cache for a year
SEMI-DYNAMIC product pages, listings
→ short TTL, or
stale-while-revalidate
PERSONALISED basket, account, prices
→ don't cache, or cache
the shell and fill client-side
The pattern that unlocks the middle row is stale-while-revalidate: serve the cached copy immediately, refresh in the background. Users always get an instant response; the content is at most one request behind.
Cache-Control: public, max-age=60,
stale-while-revalidate=600
Invalidation
The hard half, and there are three approaches:
TTL wait for expiry
simple, predictable,
always somewhat stale
PURGE explicitly evict on change
accurate, needs a hook
from your CMS or deploy
VERSIONED URLS never invalidate anything
/app.a3f9c.js new content = new URL
→ cache forever, safely
← the best option where
it applies
Versioned URLs are the only approach with no staleness problem, which is why every build tool hashes filenames. Use it for every asset you control — Caching Strategies.
Where it goes wrong
- Caching a personalised page. One user’s basket served to everyone. The failure that ends careers, and it’s usually a missing
Cache-Control: private - Caching a
Set-Cookieresponse, distributing one session to many users - Origin assumed unreachable. Nothing stops someone hitting it directly unless you restrict it to CDN IPs or a shared secret
- Geolocation confusion. The origin sees the edge’s IP, not the user’s. Read
X-Forwarded-Foror the CDN’s own header, or every visitor appears to be in one city — and analytics and geo-targeting both break - Purge-on-deploy that misses HTML. Assets update, pages don’t, and the new JavaScript runs against old markup
The measurement that matters
Cache hit ratio, segmented by content type. A low ratio on static assets is a configuration bug, not a tuning opportunity — and it’s usually the cache key.