skip to content

For preloading, how does a lazily loaded route differ from a lazily loaded component rendered inside that route?

level: middleimportance: should knowfreq 44%

answer

  1. where the loader is written down
  2. table is data, rendering is behaviour
  3. a URL names the branch's modules
  4. the component's loader needs its parent first
  5. parallel branch versus serial extra hop

basics

~20 s

A route's loader sits in the route table, so a URL alone names the modules the next screen needs. A lazy component's loader is reached only by rendering its parent, so no link can preload it.

solid answer

~50 s

The difference is where the loader is written down. A lazy **route** declares its loader in the route table beside its pattern, so the router can resolve a URL to a branch and start every matched level's module in parallel before anything renders — which is what makes preloading from a link possible. A lazy **component** declares its loader inside the parent's code, so it is unreachable until that parent's module has downloaded, executed and rendered; its request serialises after the wait the user is already paying. So anything on a screen's arrival path belongs in the table or in the route's own module, while a lazy component suits genuinely conditional UI — a dialog, a heavy editor, a panel that may never open. You can still warm one yourself on hover or idle; what you cannot do is have the router warm it from the link.

go deeper

for a junior

Know the two shapes: a route whose code loads when its URL is first visited, and a component inside a screen whose code loads when that component is first rendered.

for a middle

Explain why only the first is preloadable from a link: its loader is data in the table, while the other is reachable only once its parent has rendered.

for a senior

Use it diagnostically: for each module a screen needs, ask whether the URL names it, and move anything on the arrival path into the branch the router can start early.

for a principal

Treat it as a structural rule for the codebase — destinations are route entries, conditional UI is a lazy component — so deferral stays predictable as the app grows.

## Declared in the table versus discovered while rendering Both mechanisms defer code, and that similarity is what makes the distinction worth asking about. The difference is **where the loader is written down**. A lazy **route** puts its loader in the route table: a static structure the router can read at any time, alongside the pattern that says which URLs reach it. A lazy **component** puts its loader inside another module's code — the parent screen references it, and that reference only becomes reachable once the parent's own module has been downloaded, executed and rendered to the point of using it. So the question "what code does this URL need?" has an answer the router can compute for routes and cannot compute for lazily loaded components. That single fact drives everything else. ## What a URL alone lets the router start Given a link's URL, the router can: - match it to a branch of entries, nesting level by nesting level; - see each matched entry's loader; - start all of them at once, in parallel, before anything renders; - memoise each resolved module so the eventual click renders immediately. A lazily loaded component inside a route gets none of that from the link. Its loader is only reached during rendering, which cannot happen until the route's code has arrived. The requests therefore serialise: the route's bundle downloads and runs, and only then is the component's bundle requested. That is an extra round trip placed after the one the user is already waiting on, and no amount of preloading the *link* will move it. | | Lazy route | Lazy component inside a route | |---|---|---| | Where the loader lives | the route table, as data | inside the parent's module | | Known from the URL | yes, before anything renders | no, only while rendering | | Preloadable from a link | yes, the whole matched branch | not by the router | | Request timing | parallel with the branch's other levels | after the parent's code has run | | Natural granularity | one screen or layout level | one conditional piece of UI | ## Which mechanism fits which problem - Use a lazy **route** for anything that is a destination: a screen, a layout level, a section of the app with its own URL. The router can then both defer it and undo the deferral with a preload. - Use a lazy **component** for UI that is genuinely conditional within a screen and may never be shown: a heavy editor behind a button, a dialog, a visualisation on a collapsed panel. Deferring it is right precisely because there is no URL that predicts it. - Do not use a lazy component for something the screen shows immediately on arrival. That is code on the click path written so the router cannot start it early; either let the route's own module import it directly, or make it a nesting level in the table. ## Warming a lazy component yourself A lazy component is not unwarmable — it is just not the router's job. Once the parent screen is on screen, the same intent signals are available to you directly: start the component's loader when the button that opens it is hovered or focused, when a collapsed panel scrolls into view, or during idle time after the screen settles. The mechanics are identical to a route preload (call the loader, memoise, deduplicate); what is missing is only the router's ability to do it **from the link**, before the parent exists. ## The nesting angle Because a matched branch names a loader per nesting level, a router preloading a URL can have the layout level, the intermediate level and the leaf all in flight simultaneously. Discovering the same three levels by rendering would mean three sequential waits. This is the strongest practical argument for expressing structure as route entries rather than as components that happen to load other components: the structure becomes data the router can act on ahead of time, instead of behaviour it can only watch unfold. A useful test when reviewing a screen that feels slow to open: for every module the screen needs, ask whether the URL names it. Anything the URL does not name cannot start until something renders, and everything the URL does name could have started on hover.

  • Why can a router start every level of a matched nested branch at once?
    Because matching a URL yields the whole branch of entries, and each entry's loader is data the router can read immediately. Nothing has to render for the next level to become known, so the layout level, any intermediate levels and the leaf can all be in flight together instead of being discovered one render at a time.
  • When is a lazily loaded component the right choice despite not being preloadable from a link?
    When the UI is conditional within a screen and may never be shown: a dialog, an editor behind a button, a visualisation on a collapsed panel. There is no URL that predicts it, so deferring is honest. Warm it on the same intent signals yourself — hover or focus on the control that opens it.
  • What is the smell when a screen the user has just arrived at immediately loads another module?
    Code on the arrival path that the router could not start early. Either the route's own module should import it directly, so it ships in that branch's bundle, or the piece deserves to be a nesting level in the table, where a link's preload can reach it.

saying these in an interview costs you the question

  • Thinks a router can preload a lazy component nested inside a route from the link
  • Puts code the screen shows on arrival behind a lazily loaded component
  • Assumes a route table's nested levels load one after another rather than together
  • Believes a lazy component cannot be warmed at all, rather than not by the router
  • Treats route entries and lazy components as interchangeable ways to defer code