HTTP caching
HTTP already has a caching protocol; most bugs come from using half of it.
An endpoint with no Cache-Control gets cached by heuristic in ways you did
not choose, and an endpoint with no-cache everywhere throws away free
reuse. The job is to state freshness and validation explicitly so every
layer between server and user agrees on when a response is still good.
Method
- Classify each response by who may store it. Public and shareable gets
Cache-Control: public; per-user getsprivate; secrets and one-shot tokens getno-store. Do not confuseno-cache(store, but revalidate before use) withno-store(never write it down). The wrong one leaks or wastes. - Set
max-ageto the real tolerance for staleness. Ask how many seconds a wrong answer is acceptable, and use that number. Fingerprinted assets take a year; a pricing feed takes 30 seconds. Adds-maxagewhen shared caches should hold it longer than browsers. - Attach a validator so revalidation is cheap. Emit a strong
ETag(content hash) orLast-Modified. When the client sendsIf-None-Match, compare and return304 Not Modifiedwith no body. A 304 costs headers, not payload, and skips regeneration. - Serve stale on purpose with
stale-while-revalidate. Pair it withmax-ageso the cache returns the slightly-old copy instantly and refetches in the background:max-age=60, stale-while-revalidate=300. Addstale-if-errorto keep serving during an origin outage. - Make ETags cheap and correct. Hash the rendered body or a version
plus key, not the wall clock. Never gzip after computing the ETag on the
uncompressed body without marking
Vary: Accept-Encoding, or two encodings collide on one tag. - Verify with the actual request cycle.
curl -sIthe endpoint, then replay with-H 'If-None-Match: "<tag>"'and confirm a 304. Check that a deploy which changes content also changes the ETag or the fingerprint.
Checks
- Does every endpoint declare
Cache-Controlintentionally, with no route falling back to server defaults? - Does a repeat request with
If-None-Matchreturn 304 and an empty body? - Are
private/no-storeset on every authenticated or secret-bearing response, verified with a logged-incurl -I? - Does changing the response body change its validator?
Boundaries
This is response-header semantics for correctness and reuse. Edge topology, purging, and origin shielding belong to cdn-strategy; application-level memoization and object caches are performance-optimization territory. Browser back-forward cache quirks defer to the target browser's documented behavior.