Frontend engineer path · The network and the user · Topic 11 of 17

HTTP caching for the web

Not started

Every response can carry instructions for the caches between your server and the user: the browser's own cache, and shared caches such as a CDN or a company proxy. Cache-Control says who may store a response and for how long it stays fresh; validators such as ETag let a cache check cheaply whether a stale copy is still right. Getting these two right is most of web caching.

Why it matters: The fastest request is the one that is never made. Wrong headers either make repeat visits slow or keep users stuck on an old version of your site.

  1. 1Fresh or stale

    max-age says how many seconds a stored response stays fresh. While fresh, a cache answers without asking the server at all.

  2. 2Revalidate cheaply

    Once stale, the cache asks again with If-None-Match (the ETag it has). If nothing changed, the server replies 304 Not Modified with no body.

  3. 3Hash the file names

    Put a content hash in each asset's URL and cache it for a year with immutable. A new version gets a new URL, so nothing needs invalidating.

  4. 4no-cache isn't no-store

    no-cache means store it but check with the server before every use. no-store means don't keep it at all.

1

How it works

The explanation, with examples you can run.

Freshness

A stored response is fresh until its age passes max-age seconds. A fresh response is used straight from the cache, with no network request at all. Once it is stale, the cache must revalidate before using it. Useful directives:

  • max-age=N: fresh for N seconds. s-maxage=N does the same for shared caches only, overriding max-age there.
  • public / private: private means only the user's browser may store it (personalised pages); shared caches must not.
  • no-cache: may be stored, but must be revalidated before every use.
  • no-store: must not be stored anywhere.
  • immutable: the response won't change while it is fresh, so don't revalidate even when the user reloads.
  • stale-while-revalidate=N: after it goes stale, it may still be served for N seconds while a fresh copy is fetched in the background.

Without Cache-Control or Expires, caches may guess a freshness lifetime from Last-Modified (heuristic caching). Set the headers yourself rather than relying on that.

Revalidation

When the server sends an ETag (a version tag for the body) or Last-Modified, a cache holding a stale copy sends a conditional request with If-None-Match or If-Modified-Since. If the content hasn't changed, the server answers 304 Not Modified with headers only, and the cache updates its stored copy with them, which makes it fresh again. You still pay a round trip, but not the download.

⌘/Ctrl + Enter
OutputNot run yet

Press Run to see what your code prints, in order.

Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.

This is a simplification of RFC 9111 (it ignores Expires, Vary and heuristics), but it is the decision every cache makes.

A pattern that works

  • Hashed static assets (app.3f9a1c.js, logo.81b2.svg): public, max-age=31536000, immutable. Your build tool writes the hash; a deploy changes the URL.
  • HTML: no-cache with an ETag, or a short max-age at the CDN, so a new deploy is picked up on the next visit.
  • Personalised responses: private, and no-store for anything sensitive.
  • Responses that differ by request header (language, encoding): send Vary so a cache doesn't hand one user's variant to another.

A CDN is a shared cache close to the user. It follows the same headers, can be purged when you must, and usually adds its own controls on top. Purging is the escape hatch; hashed URLs are the plan.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Which headers suit app.3f9a1c.js, a file whose name changes whenever its content does?
  2. 2.And for index.html, which links to those hashed files?
  3. 3.An API response has Cache-Control: max-age=60, s-maxage=600. What does s-maxage change?

0 of 3 right so far. Get all three to finish this topic.