Frontend Labs
⋯

Chapter 40 — Caching: the biggest performance lever there is

In one sentence

The fastest request is the one you never make, and HTTP has a rich, precise system for saying how long a response may be reused.

The problem

Every request costs round trips (Chapter 4's latency budget), bandwidth, and server work. Most content doesn't change between requests. Caching removes the request entirely.

The three caches

   BROWSER CACHE          CDN / PROXY CACHE           ORIGIN SERVER
   (private -- one user)  (shared -- many users)      (the source of truth)
        |                        |                          |
   fastest, free            fast, near the user         slow, expensive

The private vs shared distinction runs through everything below: a response containing your bank balance may be cached by your browser but must never be cached by a shared CDN.

Cache-Control: the directives that matter

   Cache-Control: public, max-age=31536000, immutable
                  \____/  \______________/  \_______/
                  who may   how long fresh   never even
                  store it  (seconds)        revalidate
Directive Means
max-age=N Fresh for N seconds
s-maxage=N Same, but for shared caches only (overrides max-age there)
public Shared caches may store it
private Browser only — CDNs must not store it
no-cache You may store it, but check with me before every reuse
no-store Never store it anywhere. This is the real "don't cache".
must-revalidate Once stale, don't serve it — check or fail
immutable This will never change; don't revalidate even on a reload
stale-while-revalidate=N Serve the stale copy for up to N s while fetching a fresh one in the background

The single most common mistake in web development: using no-cache when you mean no-store. no-cache permits storage — it just requires revalidation. If the response contains something secret, you want no-store.

Revalidation: the 304

When a cached copy goes stale, the client doesn't re-download blindly — it asks "has this changed?"

   Original response:                Later request:
     ETag: "v3-abc123"                 If-None-Match: "v3-abc123"
     Last-Modified: Tue, 01 ...        If-Modified-Since: Tue, 01 ...

   Server, if unchanged:
     HTTP/1.1 304 Not Modified
     (headers only -- NO BODY)

   -> the client reuses what it already has.
      A 2 MB image cost ~200 bytes to confirm.

ETag is a version token — usually a hash of the content. Last-Modified is a timestamp (1-second resolution, so ETag is more precise).

304 is not an error. Seeing lots of them in your logs means caching is working.

Vary: the header that keeps caches honest

A cache stores one response per URL. But what if the response depends on a request header?

   Same URL, different responses:
     Accept-Encoding: gzip     ->  compressed bytes
     Accept-Encoding: (none)   ->  plain bytes

   Without "Vary: Accept-Encoding", a cache might hand compressed
   bytes to a client that can't decompress them.
   Vary: Accept-Encoding     almost always needed if you compress
   Vary: Accept-Language     one cached copy per language
   Vary: Cookie              -> effectively uncacheable in a shared cache
   Vary: User-Agent          -> cache explosion; avoid

The pattern that actually works

This is the standard modern setup, and it's worth copying exactly:

   ASSETS (JS, CSS, images) -- give them a content hash in the filename:
       /static/app.9f3c2a1.js
       Cache-Control: public, max-age=31536000, immutable
       -> cached for a year, never revalidated.
          Content changed? The FILENAME changes, so it's a different URL.

   HTML -- the file that references those assets:
       Cache-Control: no-cache
       -> always revalidated (cheap: a 304 if unchanged),
          so a deploy is visible immediately.

   API responses (personal data):
       Cache-Control: private, no-store

This gives you near-perfect caching and instant deploys, with no purging.

Practice (30 min)

A. Look at real caching headers:

curl -sI https://example.com | grep -iE 'cache-control|etag|last-modified|age|vary|expires'

Try a few sites and compare. Look at a big site's static assets versus its HTML.

B. Do a conditional request yourself and get a 304:

URL=https://example.com
ET=$(curl -sI $URL | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
echo "ETag is: $ET"
curl -sI -H "If-None-Match: $ET" $URL | head -1      # expect: 304 Not Modified

C. See a CDN hit vs miss:

curl -sI https://www.cloudflare.com | grep -iE 'cf-cache-status|age'
curl -sI https://www.cloudflare.com | grep -iE 'cf-cache-status|age'   # run twice

Age: tells you how many seconds the cache has held it.

D. In your browser: DevTools → Network. Reload a page and look at the Size column — entries saying "(disk cache)" or "(memory cache)" cost zero network. Then tick Disable cache and reload; watch the load time and transfer size change. That difference is what caching is worth.

Common confusions

  • "no-cache means don't cache." It doesn't. no-store does.
  • "Why is my update not showing?" Something along the path cached it with a long max-age and no revalidation. This is why hashed filenames exist.
  • "Should I use Expires or Cache-Control?" Cache-Control — it's newer and wins when both are present. Expires is an absolute date and suffers from clock skew.
  • "Does HTTPS prevent caching?" No. Browsers and your own CDN cache HTTPS responses normally. Only unrelated intermediaries can't.

Check yourself

  1. What's the difference between no-cache and no-store?
  2. What is an ETag and what does a 304 save you?
  3. Why does Vary: Accept-Encoding matter?
  4. Why do modern sites put a hash in asset filenames?

Further reading

  • Article (best on the topic): "Caching best practices & max-age gotchas" — Jake Archibald. Explains the hashed-filename pattern definitively.
  • Docs: MDN, "HTTP caching" — thorough and accurate.
  • Book (free): High Performance Browser Networking, the HTTP chapters.
  • Tool: redbot.org — paste a URL, get an expert analysis of its caching headers and what's wrong with them. Genuinely useful.

Generated from real-life-guide-v1.md · 8 Oct 2026 16:36