skip to content

How does a router preload a lazily loaded route's code before the click, and which signals typically trigger it?

level: middleimportance: should knowfreq 56%

answer

  1. the loader, called without navigating
  2. match the link, start the branch
  3. signals ranked by confidence
  4. must deduplicate with the real click
  5. hover, focus, viewport, idle

basics

~20 s

It resolves a link's URL through the route table, then calls the matched entries' loaders early without navigating. Typical triggers are pointer hover or touch start, keyboard focus, the link entering the viewport, and idle time.

solid answer

~50 s

A preload is simply the loader called outside a navigation. Given a link's URL the router matches it against the table exactly as a click would, starts the loaders for the matched branch and memoises the results, without touching the current screen, the URL or history. The click then commits with no pending period — and because it is the same code path, a preload **deduplicates**: a click arriving mid-flight waits on the existing request instead of issuing a second. The triggers are intent signals of differing confidence: hover or touch start is strong, keyboard focus equally so and keeps the optimisation from being mouse-only, a link merely entering the viewport is weak, and idle time is not intent at all but a policy naming likely-next routes. Where the route declares a data loader too, the same hook can start it — a separate concern.

go deeper

for a junior

Recall that the router can start fetching a link's route code before the click, most often when the pointer hovers the link or the link receives keyboard focus.

for a middle

Explain the mechanism: resolve the URL against the table, call the matched branch's loaders, memoise, and deduplicate with a click that arrives mid-flight.

for a senior

Show restraint and measurement: rank signals by confidence, cap concurrency, keep speculation behind the current screen's own work, and report hit rate per signal.

for a principal

Discuss it as a spend you govern, with thresholds agreed before the numbers arrive and an owner for rules that otherwise only ever accumulate.

## A preload is the loader called early A lazy route entry holds a loader function, and a navigation is the only thing that *normally* calls it. Preloading is the observation that nothing forces that: given a URL, the router can match it against the route table exactly as it would on a click, then call the loaders for every entry in the matched branch and memoise the results — without changing the current screen, the URL or the history. The consequence at click time is that the entry already holds a resolved module, so the navigation commits with no pending period. If the click lands while the request is still in flight, a correctly implemented preload **deduplicates**: the navigation awaits the existing request instead of starting a second one, so a preload that was only half-finished still removes most of the wait. Two things make this possible, and both belong to the route table rather than to the link: the pattern is declared as data, so a URL can be resolved to a branch ahead of time; and the loader is an ordinary function, so calling it is not a navigation. ## The intent signals, ranked by confidence | Signal | Strength of intent | Cost when wrong | Typical fit | |---|---|---|---| | Pointer hover or touch start on a link | strong | one branch per hovered link | navigation bars, cards, table rows | | Keyboard focus on a link | strong | one branch per focused link | keyboard and assistive-tech users | | A link scrolled into the viewport | weak | many branches at once in a long list | short pages with few distinct targets | | Idle time after the current screen settles | not intent at all, a policy | the routes you nominated | a small named set of likely-next routes | Hover and focus are the same class of evidence — a user reaching for a link — and treating focus as a first-class trigger is what keeps the optimisation from being mouse-only. Viewport entry is much weaker: in a list of fifty links, fifty branches become candidates, and the user will take one. Idle preloading is not a reaction to the user at all; it is you nominating routes in advance, so it should name a handful of destinations rather than the whole table. ## Why preloads must not outrank the real work A preload is speculative, and it competes for the same connection, CPU and cache as requests the user is definitely waiting for. Practical guardrails: - Start preloads only once the current screen's own critical work has finished, or during idle time. - Cap how many preloads may be in flight, especially for viewport-triggered ones. - Do not restart a loader that is already in flight or already resolved; a preload hook must be cheap to call repeatedly, because pointer movement will call it repeatedly. - Cancel or simply ignore a hover that ends after a few tens of milliseconds; a pointer crossing a menu on its way elsewhere is not intent. - Respect an explicit user preference for reduced data use, and treat a slow connection as a reason to preload less, not more. ## What a preload does and does not cover A route branch's **code** is what this mechanism guarantees. Where a route also declares a data loader, the same hook is usually able to start it on the same signal, and that is a different concern with its own timing questions — the two are often triggered together but they are not the same thing, and a preload that warms code while the data request still waits for the click leaves half the wait in place. Preloading also does nothing for code the router cannot see: anything loaded from inside the route's own rendering, rather than declared in the table, is not part of the matched branch and cannot be started from the link. ## Making it measurable Preloading is easy to add and easy to leave mis-tuned, so instrument it: 1. Count preloads issued against preloads actually used by a subsequent navigation. A low hit rate on a signal is a reason to demote or drop that signal, not to widen it. 2. Watch the navigation wait itself — the time from click to committed screen — split by whether the target had been preloaded. That is the number the work exists to move. 3. Watch bytes and requests issued while the user is idle on a screen, to catch a viewport rule that has quietly turned into "download everything". The honest summary: preloading trades speculative bandwidth for a removed wait, and the signal you choose is the exchange rate.

  • Why must a preload hook be safe to call many times for the same link?
    Pointer movement and repeated focus will call it repeatedly. It must return early when the entry's module is already resolved, and join the existing request when one is in flight, so the work is done once. Without that deduplication a dense navigation bar can put a dozen redundant requests on the wire.
  • Why is keyboard focus treated as a first-class preload trigger alongside hover?
    Because it is the same class of evidence — a user reaching for a link — expressed without a pointer. A policy that preloads only on hover silently gives keyboard and assistive-technology users the slower path, which is both an equity problem and an easy one to fix, since the router's hook is identical.
  • When is viewport entry a poor trigger?
    Whenever many distinct destinations are visible at once. In a long list or an infinite feed, scrolling makes dozens of branches candidates while the user takes one, so bandwidth and requests are spent on guesses. It is defensible on a short page with a handful of targets, and should be decided per surface rather than globally.

saying these in an interview costs you the question

  • Thinks preloading is a different mechanism rather than the loader called early
  • Preloads only on pointer hover, leaving keyboard users on the slow path
  • Issues a second request when the click lands while a preload is still in flight
  • Preloads every link in the viewport on a long list and calls it an optimisation
  • Lets speculative requests compete with the current screen's own critical work
  • Believes preloading the code also removes the wait for the route's data