What changes when a screen's data is requested at the route boundary before that screen renders, rather than inside its components?
answer
- the router owns the timing
- wait moves in front of the screen
- one place knows every request
- slowest blocking call sets navigation cost
basics
~20 sThe wait moves in front of the screen: a loader on the route starts the screen's requests before the destination renders, so the screen arrives complete and with no internal waterfall. The price is a slower-feeling navigation.
solid answer
~50 sA router can run a **loader** or resolver for the matched route and only commit the navigation once it resolves. Because the loader is a single place that knows what the screen needs, it can start the independent requests together, so the screen renders with data and no component adds a round trip of its own. That converts many small waits inside the screen into one wait in front of it — and during that wait the user is still looking at the previous screen, not at a half-drawn new one. The tradeoffs are real: the whole navigation is as slow as the loader's slowest request, the loader must be told what to do about a failure before any of the new screen exists, and data only some components need now blocks everyone unless you deliberately let it resolve later.
go deeper
Learn the two shapes: data asked for by the route before the screen appears, versus data asked for by each component after it appears. Know which one shows the previous screen while waiting.
Explain why one declaration site lets independent requests overlap, and that the navigation then costs the slowest of them rather than their sum.
Demonstrate the split in practice: which requests are allowed to block a navigation, which resolve into the screen afterwards, and how a slow optional call gets demoted.
Own the policy. Decide where request definitions live so routes can start them and components can read them, and set a rule for what may ever block a transition.
**Fetch-then-render at a route boundary** means the router, not the component, owns the timing. When a navigation is matched, the router runs an attached function — a loader or resolver — starts the requests that screen needs, and holds the transition until they settle. Only then is the new screen rendered, with its data already in hand. ## What moves, precisely - **The start time.** Requests leave the device when the route is matched, which is earlier than any component of the destination exists. - **The declaration site.** One function per route says what the screen needs, so the set is knowable without rendering the tree. - **The waiting screen.** Since the destination has not rendered, what stays on screen is the *previous* screen — usually with some pending indication — rather than a skeleton of the new one. - **The failure site.** A request that fails fails before the screen exists, so the router decides what to show; a component-level failure can only be handled inside a screen that already rendered. ## Why the waterfall disappears A waterfall inside a screen comes from a component's request being unable to start until another component's data arrives. When all of a screen's requests are declared in one function, that function can start the independent ones together and await them as a group, so their latencies overlap instead of stacking. The screen's wait becomes the slowest single request, not the sum. Two things do **not** vanish: 1. **Genuine data dependencies.** If one request needs an id from another's response, the loader still runs them in sequence — it just does so visibly, in one readable place. 2. **Requests declared elsewhere.** A component inside the screen that still fetches on its own re-introduces a round trip after render. Mixing the strategies without deciding which owns what is how a screen ends up with both a slow navigation *and* internal waterfalls. ## The comparison interviewers are looking for | | Fetch inside components | Fetch at the route boundary | |---|---|---| | Request starts | after the component renders | before the destination renders | | Screen during the wait | new screen, no data | previous screen, pending indication | | Internal waterfall | one trip per dependent level | only real dependencies | | Navigation feels | instant, then fills in | delayed, then complete | | Slowest request | delays its own subtree | delays the whole navigation | | Who handles failure | the component | the route, before the screen exists | | Cancelling a superseded navigation | the component must arrange it | the router owns the transition | ## The cost you must name The coarseness is the whole tradeoff. A loader that awaits everything makes the navigation hostage to its slowest call, and users read a delayed navigation as a broken click unless something acknowledges the press. Most routers therefore let a loader return a value that is still settling, so the screen can commit with its critical data and let the rest arrive into a boundary inside the screen. That is the useful middle: **block on what the screen is meaningless without, stream the rest.** Deciding which is which is a product judgment, not a technical one — the headline and the price belong in the blocking set; a recommendations rail does not. A second cost is coupling. The loader must know the screen's data needs, so moving a component between screens now means editing route configuration too. Teams answer that by keeping the *request definition* shared — a named, parameterised description of a request that both the loader and the component can refer to — so the route decides *when* to start it while the component decides *how to read it*. ## Where this leaves render-as-you-fetch Fetch-then-render still puts a wait in front of the screen, because the request starts at match time and nothing of the destination renders until it is done. Starting the same request slightly earlier — at the moment the user showed intent, before the navigation is even committed — and letting the destination render immediately against a result that may still be in flight is the third strategy. It has no ordering wait at all, at the cost of requiring the framework to have a way for a component to say *not ready yet*. ## How to answer it Say where the wait went rather than claiming it was removed, then name the two prices: the navigation is as slow as the slowest blocking request, and everything the loader awaits is now everyone's problem. Finish with the split — block on the essential, let the optional resolve into the screen — because that is what a production route configuration actually looks like.
- If the user starts a second navigation while the first route's loader is still running, what should happen?The router owns the pending transition, so it abandons the superseded one and commits the latest destination. Because the requests were started by the route rather than by a mounted component, there is a single place that knows they are obsolete, which is one of the quieter advantages of moving the timing to the boundary.
- Does fetching at the route boundary help a screen whose slowness comes from one request needing another's id?Only in clarity, not in time. A real dependency still runs in sequence wherever it is declared; the loader just makes it visible in one function instead of hidden across two components. Removing that wait needs a contract change, such as one response carrying both parts.
saying these in an interview costs you the question
- Says a route loader removes the wait rather than relocating it
- Claims the screen shows a skeleton of itself while the loader runs
- Assumes a loader parallelises requests that genuinely depend on each other
- Blocks the navigation on optional data such as a recommendations rail
- Forgets that components still fetching on their own re-add a waterfall
- Thinks a loader cannot start anything until the component tree exists