Subtle caching mistakes cause data leaks, stale deploys and origin overloads at exactly the worst moments.
A cache stores responses under a cache key: by default the method and URL. Vary adds request headers to the key: Vary: Accept-Encoding stores gzip and Brotli versions separately. Vary: Cookie or Vary: User-Agent makes nearly every request unique and destroys the hit rate. Instead, normalise at the CDN, for example by mapping user agents to a "mobile" or "desktop" bucket, or by splitting URLs.
Age and freshness. A response is fresh while its age is below its freshness lifetime: s-maxage (shared caches), then max-age, then Expires. Without any of these, caches may apply heuristic freshness, often 10% of the time since Last-Modified. That's why responses without explicit headers can be cached unexpectedly; always send Cache-Control.
Serving stale on purpose:
stale-while-revalidate=60: after expiry, serve the stale copy for up to 60 s while fetching a fresh one in the background. Users never wait for revalidation.stale-if-error=86400: if the origin fails, keep serving stale content for a day.
Request collapsing. When a popular item expires, CDNs merge simultaneous misses into one origin request, protecting the origin from a stampede.
Invalidation strategies, best first: immutable hashed URLs (no invalidation needed); short TTLs plus SWR; tag-based purges (Surrogate-Key or Cache-Tag headers, purged by tag on publish); URL purges. Clear-Site-Data: "cache" wipes a user's browser cache, for example on logout.
Cache-Control: private content can still sit in the browser cache on shared machines; use no-store for truly sensitive pages.
Example
# A news article: browsers revalidate, CDN keeps it 5 min, serves stale while refreshing
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60, stale-if-error=86400
ETag: "article-812-v14"
Vary: Accept-Encoding
Cache-Tag: article-812, section-tech # provider-specific: purge by tag on edit
# Fingerprinted asset: never revalidated, never purged
# /assets/app.3f9a1c.js
Cache-Control: public, max-age=31536000, immutable
# Account page with personal data
Cache-Control: no-store
# Anti-pattern: every distinct cookie value becomes a separate cache entry
Vary: CookieRemember
- Cache key = method + URL + Vary headers; keep Vary minimal
- Always send Cache-Control to avoid heuristic caching
- stale-while-revalidate hides revalidation latency; stale-if-error survives outages
- Prefer hashed URLs and tag purges over URL purges
Watch out for
- Adding Vary: User-Agent and silently dropping the CDN hit rate to near zero.