skip to content

When is fetching in the browser the right choice on a server-rendered page, and when does it recreate the waterfall the route read removed?

level: middleimportance: should knowfreq 52%

answer

  1. when it starts, not where
  2. render position gates the request
  3. child waits for parent's response
  4. personal, live, or deferred data
  5. hoist the read, seed the rest

basics

~20 s

Fetch in the browser for data that is personal, live, or only needed after an interaction. It becomes a waterfall when a request can only start once its component renders, so a nested component's request waits on its parent's.

solid answer

~50 s

Client fetching earns its place when the data should not sit inside a shared document, when it keeps changing while the page is open, or when nobody needs it until the user acts — a filter, a later page of a list, a panel that opens. It backfires when the request's *start* is tied to render position. A component that fetches on mount cannot begin until it renders, and a child that needs its parent's result cannot begin until the parent's request resolves, so the page pays a round trip per level after load. That is exactly the serial chain a single route-level read collapses into one server-side step. The tell is a network panel that fills in staircase steps after load rather than in parallel. Hoist those reads to the route and seed them; leave in the browser only what genuinely cannot be known at render time.

go deeper

for a junior

Remember what browser fetching is for: data that is personal, changing while the page is open, or not needed until the user does something. Everything else the server can read once.

for a middle

Explain that the cost is about when a request can start. A request issued as a component renders waits for that render, and a child request waits for its parent's response.

for a senior

Diagnose it from evidence — staircase timings in the network panel after load — and fix the dependency rather than only relocating the call.

for a principal

Set the default so teams do not decide per component: first-screen data is read at the route and seeded, browser fetching is reserved for personal, live and deferred data, and exceptions are argued.

A server-rendered page with a seeded cache starts with its data already in hand. Adding browser-side fetching to such a page is not wrong — some data has no business in the document — but the way it is usually added quietly rebuilds the request chain that rendering on the server was supposed to remove. ## What client fetching is genuinely for - **Data too personal for a shared document.** If the response might be stored or reused, a per-user value fetched by the browser after load never enters that artifact. - **Data that changes while the page is open.** Polling, subscriptions, anything with a live feel: the seed is a first value at best, and the refresh has to run in the browser regardless. - **Data nobody needs yet.** The options of a menu that has not been opened, the second page of a list, a detail panel behind a click. Loading it during the render makes the first response slower for everyone to serve a minority of sessions. - **Data too slow or too large to hold up the document.** Fetching it after load trades a complete-but-late page for a fast page that fills in, which is usually the better trade. ## How the waterfall comes back The damage comes from *when the request can start*, not from where it runs: 1. A component issues its request as an effect of rendering, so nothing is in flight until that component renders. 2. A nested component needs an identifier from its parent's result, so its request cannot even be formed until the parent's response arrives. 3. Each level adds a full network round trip *after* the page is already interactive, and each one may show its own loading state on top of already-rendered content. A single route-level read avoids this by knowing the whole page's data needs up front and issuing them together, close to the data. Re-introducing per-component fetching hands that advantage back one component at a time. | Shape | When requests start | Cost on a three-level page | |---|---|---| | One route-level read, seeded into the cache | before the response is produced, in parallel on the server | one server-side step; nothing after load | | Per-component fetch on mount, independent | after the page is interactive, in parallel | one client round trip | | Per-component fetch on mount, each depending on its parent | after the page is interactive, one level at a time | three chained client round trips | ## Telling a real need from a habit | Question | If yes | If no | |---|---|---| | Is the value knowable at render time? | it can be read on the server and seeded | it must be fetched in the browser | | Does the first screen display it? | seed it | fetch it when the interaction happens | | Does it change while the page is open? | fetch or refresh it in the browser | a seeded snapshot is enough | | Would it be unsafe inside a shareable document? | keep it out; fetch per user | seeding is fine | ## Fixes when the staircase is already there - **Hoist the read.** Move it to the route's server-side read, where sibling reads run together, and let the handoff seed it. The component then reads from cache instead of from the network. - **Break the dependency, not just the timing.** If a child's request needs an identifier the parent fetched, that chain persists in the browser. Resolve it on the server, or make the identifier available from the route's own parameters so both requests can start at once. - **Start requests from events, not from depth.** A request tied to a click, a hover or a route transition begins when the intent exists, rather than when a subtree happens to render. - **Prefetch on intent.** For data behind an interaction, beginning the request on hover or focus removes the wait without loading it for everyone. - **Keep the seed on screen during revalidation.** If a hoisted read is later refreshed in the browser, replacing it in place avoids reintroducing the loading state the seed removed. The honest summary for an interview: client fetching is a tool for data that is personal, live, or deferred. Anything else that ends up there is usually a component-shaped habit, and the price is paid in round trips the server render had already spent.

  • How would you recognise a re-created waterfall from the browser's network panel?
    Requests that begin after the page is already interactive and start in a staircase — each one's start time aligned with the previous one's finish — rather than together. Independent client requests appear as a parallel block; chained ones appear as steps, one network round trip per level of the dependency.
  • A child's request needs an identifier that only its parent's response contains. What actually fixes it?
    Removing the dependency, not just moving the code. Resolve both on the server in one step, or expose the identifier through the route's own parameters so both requests can be formed immediately. Hoisting a chained pair without breaking the chain just relocates the same serial wait.

saying these in an interview costs you the question

  • Calls any browser-side fetching on a server-rendered page a mistake.
  • Thinks parallel client requests and chained ones cost the same.
  • Fetches first-screen data on mount when it was knowable at render time.
  • Assumes seeding alone prevents a waterfall from forming later.
  • Loads interaction-only data during the render to avoid a spinner.