A user renames their account. The settings page shows the new name at once. The dashboard still shows the old name after a full page navigation and corrects itself only after a hard reload or about 15 minutes. Two different caches are involved. Which is which and which one can application code not invalidate?
Show the full answer Hide the answer
The two caches
The application data-layer cache lives in memory inside the page. It is keyed by entity or query, it disappears on navigation, and your code can clear or update it after a mutation. That is why the settings page was right: it wrote the new name into the cache it owns.
The browser HTTP cache lives on disk, is shared across tabs and navigations, and is keyed by request URL plus
whatever Vary says. It is governed entirely by the response headers the server sent. Application code cannot
invalidate it, because when it holds a fresh entry the fetch call never reaches the network — there is no
request to intercept and nothing to clear.
The mechanism
The dashboard's GET /api/me came back with Cache-Control: max-age=900. For 900 seconds the browser answers
that request from disk. The data layer is empty after the navigation, asks for the resource, and is handed a
15-minute-old body. A hard reload sends Cache-Control: no-cache, which forces revalidation, which is why the
page appears to fix itself when a user does the one thing support always suggests.
The decision rule
- Authenticated JSON gets
Cache-Control: no-store, orprivate, no-cachewith anETag. Revalidation costs one round trip — roughly 30-80 ms on a decent mobile network — and a304carries no body, so you keep most of the saving without a staleness window you cannot cancel. - Freshness windows belong on immutable things. A content-hashed bundle gets
Cache-Control: max-age=31536000, immutable, because its URL changes when its content does. - A
max-ageon a per-user response is also a security bug the moment a shared cache is in the path: withoutprivate, a CDN can store one user's body under a URL another user requests.
The failure mode to remember
A third cache can outlive both: a service worker that precached the API response survives a hard reload
entirely, so the fix becomes a new service worker deployment plus a skipWaiting call.
When this is the wrong answer
For a public, non-personalised endpoint — a product catalogue, a status feed — a short max-age at the HTTP
layer is the cheapest cache you will ever get and the right first move. The rule only tightens when the response
depends on who is asking.
Common weak answers
- "Clear the client cache on mutation." Correct and insufficient: the next request is answered before it leaves the process.
- "Add a cache-busting query parameter." It works by making a new cache key, so every caller must remember to do it, and the CDN hit rate goes to zero. Fix the header instead.