A prerendered page needs one per-visitor element; what do you gain and lose by rendering that part in the browser instead?
answer
- keeps the route shareable
- personal bytes leave the initial HTML
- HTML, then bundle, then data request
- reserve the slot or accept the jump
- a new authenticated endpoint appears
basics
~10 sYou keep the route static, shared-cacheable and cheap. You pay a waterfall after the bundle loads, an element missing from the initial HTML, layout shift or a wrong-state flash, and a new authenticated endpoint.
solid answer
~50 sMoving the personal part into a browser-side fetch is the standard escape hatch: the route stays prerendered, so one stored output serves everyone and a shared cache or static host can front it, while the per-visitor bit arrives after load. The costs are real. That content is absent from the server-rendered HTML, so crawlers and no-script visitors never see it, and it arrives only after HTML, then JavaScript, then the data request — slower than a per-request render would have been. You must reserve space for the placeholder or accept layout shift, and a naive version flashes the signed-out state. You also own a new endpoint that must authenticate the caller and stay out of shared caches. It is the right call for a small, non-indexable garnish, and the wrong call when it *is* the main content.
go deeper
Know the shape: the page is prerendered and shared, and the personal piece is fetched by the browser after load, so it is not in the HTML the server sent.
Explain the waterfall — HTML, bundle, then the data request — and why the slot needs reserved space to avoid a visible jump when the value arrives.
Show the production judgment: indexing and link previews, the flash of the wrong state, and the authenticated, non-shared-cacheable endpoint you have just created.
Frame it as where you put per-visitor work across the whole app, and note that hiding a slow data source behind a client fetch moves the wait onto the user's device instead of removing it.
## The escape hatch When a route is almost entirely shareable but carries one per-visitor element — a cart badge, a greeting, a saved-items marker — you have three options: render the whole route per request, mix prerendered and per-request parts in one response if your framework supports that, or keep the route prerendered and fetch the personal part from the browser after load. The third is the portable one: it works on any target, including a plain static file host, which is why it remains the default escape hatch. The same pattern scales up to a whole route rendered only in the browser: the server sends an empty shell and the client builds everything. That is the extreme version, with the same trade-offs amplified. ## What you gain - **The route stays static.** One stored output for everyone, servable from a shared cache, a CDN edge, or a file host with no server runtime at all. - **Cost stops scaling with views.** Traffic multiplies cache hits, not renders. - **Fast first paint.** The shareable content is already in the HTML and can be delivered from the closest cache. - **Deployment freedom.** No per-request compute requirement means the route survives a target with no server at all. ## What you pay - **A request waterfall.** HTML, then the JavaScript bundle, then the data request — three sequential steps before the personal part can appear. A per-request render would have delivered it in the first response. - **It is not in the HTML.** Crawlers that do not execute scripts, link previews, and no-script visitors see the placeholder. For a cart badge that is fine; for the page's main content it is a decision about indexing. - **Layout shift and wrong-state flash.** The slot is empty or generic until the data lands, so reserve the space, and be aware that rendering the signed-out state first means signed-in visitors briefly see the wrong thing on every navigation. - **A new public surface.** The endpoint the browser calls must authenticate the caller, must not be served from a shared cache, and needs its own rate limiting and error handling. - **Client complexity.** Loading, empty, error and stale states all now exist in the browser and all need designing. | | Whole route per request | Prerendered plus browser fetch | |---|---|---| | Personal data in first HTML | Yes | No | | Shared cache hit for the page | No, without deliberate headers | Yes | | Cost per view | A render | A cache hit plus one small request | | Time to personal content | One round trip | Three sequential steps | | Runs on a static-only host | No | Yes | ## Reserving space is not optional The placeholder must occupy the final element's box. A badge that appears and pushes a header row down is a visible jump and a measurable layout-shift penalty, and it is entirely avoidable: fixed dimensions on the slot, a skeleton of the same size, or rendering the element in a neutral state rather than omitting it. ## Choosing well Ask four questions, in order: 1. **Does a crawler or link preview need this content?** If yes, do not move it to the browser. 2. **Is it the primary content or a garnish?** A price and stock level is primary; a cart count is a garnish. 3. **Does being briefly wrong cause harm?** A greeting flashing from generic to personal is cosmetic; a permissions-shaped element flashing the wrong affordance is not, and should not be rendered optimistically at all. 4. **What does the deployment target actually offer?** On a static-only host this is not a trade-off, it is the only option. If all four point the wrong way, the honest answer is to let the route be per-request and pay for it, or to split the page so the personal region has its own boundary. Some frameworks let a single route serve a prerendered shell with per-request holes, which collapses this trade-off; how that works belongs to each framework's own documentation. ## The anti-pattern to name Moving the render to the browser **to hide a slow data source** solves nothing: the wait moves from the server to the user's device, over a slower network, after the bundle has loaded. The escape hatch is about *shareability*, not about latency.
- Why is the personal part slower to appear than it would be from a per-request render?Because it arrives at the end of a sequential chain: the browser must receive the HTML, download and execute the JavaScript, then issue the data request and wait for it. A per-request render does that data read on the server before the first byte, so the content is in the initial response rather than three steps later.
- What must be true of the endpoint the browser calls for the personal data?It must authenticate the caller rather than trusting the page it came from, and its responses must stay out of any shared cache — mark them private and non-storable so an intermediary cannot serve one visitor's data to another. It also becomes a public surface that needs rate limiting and sensible error responses.
- When is browser-only rendering the wrong escape hatch entirely?When the content must be in the HTML — indexable pages, link previews, anything a no-script visitor needs — or when showing a briefly wrong state is harmful, such as an affordance that implies a permission the visitor may not have. In those cases render per request, or give that region its own boundary.
saying these in an interview costs you the question
- Thinks moving a slow fetch to the browser makes the page faster.
- Leaves no reserved space for the element that arrives later.
- Assumes crawlers always execute the page's JavaScript.
- Forgets the new endpoint needs its own authentication.
- Lets the personal data response be stored by a shared cache.
- Renders the signed-out state first without considering the flash.