In a meta-framework, why do a prerendered route and a per-request personalized route get different cache headers on their responses?
answer
- the header is a claim about the bytes
- who may keep it, and how long
- identical for everyone, or not
- a stable URL is not a fingerprinted one
- the app's copy and the front copy differ
basics
~20 sHow a response was produced decides who may keep it. A document identical for everyone may be stored by a shared cache in front of the app; one assembled from a session must be marked unstorable there.
solid answer
~50 sThe rendering mode is the framework's evidence about a response. A route prerendered at build time, or regenerated on a window, produces bytes that do not depend on the requester, so the framework emits headers that let a shared cache store it and reuse it for everyone, usually with a short shared lifetime and permission to serve the stored copy while it refreshes. A route rendered per request from a cookie or header produces bytes that belong to one person, so it is marked private or not storable at all, and any request input it varies on is declared with `Vary`. Getting this wrong in either direction is expensive: too permissive leaks one user's page to another, too restrictive means every request reaches the origin and the whole benefit of a cache in front disappears.
code
http · 10 lines# prerendered, identical for every visitor, replaceable later
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public, s-maxage=600, stale-while-revalidate=86400
# rendered per request from a session cookie
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: private, max-age=0, must-revalidate
Vary: Cookiego deeper
Learn the single distinction: a response that is the same for everyone can be kept by a cache in front of the app, and one built from a visitor's session cannot. Know that the headers are how that is said.
Explain how each rendering mode implies its policy, what a shared cache keys on, and why a response varying by a request header must declare it. Be able to describe why a stale copy can outlive an app-side update.
Show you audit the pair together: when personalisation is added to a route, the caching claim changes with it. Talk about the cost of the safe-looking choice of caching nothing.
Own it as policy. Decide which routes may be shared-cacheable at all, how personalisation is delivered without poisoning shared copies, and what shared lifetimes the organisation can actually purge within.
## The header is a claim about the response A cache sitting between the browser and the application cannot inspect an app's rendering decisions. All it has is the response and its headers, so the headers are how the application declares two things: **who is allowed to keep this**, and **for how long**. A meta-framework is in an unusually good position to make that declaration, because it knows exactly how each response was produced. That is why the same application sends different caching policies from different routes without anyone writing them by hand: the policy is derived from the rendering mode. ## The modes and the claim each one supports | How the response was produced | What is true of the bytes | The claim the headers make | |---|---|---| | Prerendered at build time | identical for every visitor until the next build | any shared cache may store and reuse it | | Prerendered and regenerated after a window or on demand | identical for everyone, but replaceable at any time | storable, short shared lifetime, may be served while refreshing | | Rendered per request from public inputs only | depends on the URL, not on the visitor | storable, usually briefly | | Rendered per request from a session, cookie or header | belongs to one person | not storable by a shared cache | | A build-fingerprinted asset file | its URL changes whenever its content does | storable effectively forever | The last row is worth separating: assets whose filename carries a content fingerprint can be given very long lifetimes precisely because a change produces a **different URL**. A document lives at a stable URL, so the same policy applied to it means an old copy keeps being served with no way to supersede it. ## What a shared cache in front actually does A shared cache stores a response under a key derived from the URL, plus whichever request headers the response declared with `Vary`. It then serves that stored copy to **anyone** who asks for the same URL until its own lifetime expires or something removes it. Three consequences follow, and they are the substance of this topic: - The application's own cached copy and the shared cache's copy are **independent**. The app rebuilding a page does not make the copy in front of it new; a stored response ages on its own clock, which is why responses coming from a shared cache report an `Age`. - Getting a personalised response stored by a shared cache is not a subtle bug, it is a **data leak**: the next person asking for that URL is handed someone else's page. This is why frameworks default to a restrictive policy for anything that reads request identity, and why adding personalisation to a route that used to be public is a change to its caching claim. - Declaring the wrong lifetime is a commitment you cannot take back from the client side. Anything already stored elsewhere keeps being served until it expires or is explicitly removed. ## The knobs, briefly The mechanism belongs to HTTP, not to any framework: a response says whether a shared cache may store it at all, how long it may be reused without asking again, whether a stale copy may still be served while a refresh happens in the background, and, through `Vary`, which request inputs make a different response. Frameworks differ in how much of this they set for you — some emit a complete policy per mode, others emit a conservative default and expect the deployment target or a configuration layer to sharpen it — but all of them are describing the same underlying question: is this response the same for everyone, and for how long is it good? ## Where people get it wrong 1. **Assuming the framework's cache and the cache in front are one thing.** They are two stores with two lifetimes; only one of them is yours to purge from inside the app. 2. **Sending a generous shared lifetime from a route that later grows a personalised element.** The mode changed and the claim did not. 3. **Turning caching off everywhere to be safe.** That is a real availability decision: every request now renders, and a traffic spike lands entirely on the application. 4. **Using an asset policy on a document.** Long lifetimes work on fingerprinted URLs, not on stable ones. 5. **Forgetting `Vary` on a response that genuinely differs by a request header**, so one variant is stored and served to everybody. The habit worth building is to read a route's rendering mode and its caching headers together. If they disagree, one of them is wrong, and the headers are the half the outside world obeys.
- A public route gains a small signed-in greeting. What has to change?Its caching claim. The response now depends on the requester, so it can no longer be stored and reused by a shared cache, and whatever request input it varies on has to be declared. The usual alternatives are to keep the document public and fetch the personal part from the client, or to render the personal part in a separately addressed response.
- Why can a long shared lifetime be safe on an asset but dangerous on a page?Because an asset built with a content fingerprint gets a new URL whenever its content changes, so an old copy is simply never requested again. A page lives at a stable URL, so a long lifetime means the stored copy keeps being served with no natural replacement, and the only ways out are waiting for expiry or removing it explicitly.
- If the framework rebuilds a cached page, does the copy in front of it update?No. The cache in front stored a response and will keep serving it until its own lifetime runs out or something removes it. That is why a refresh inside the application and a removal from the cache in front are two separate steps in any correct publishing path.
saying these in an interview costs you the question
- Treating the app's cache and the cache in front as one store
- Sending a shared lifetime from a response built on a session
- Applying a fingerprinted-asset policy to a document URL
- Omitting Vary on a response that differs by request header
- Disabling caching everywhere and calling it a safe default
- Believing an app-side refresh replaces copies already stored elsewhere