skip to content

What triggers a client router to prefetch a link's route payload, and what does that speculation cost?

level: middleimportance: must knowfreq 66%

answer

  1. speculative work, paid up front
  2. viewport guesses early, intent guesses late
  3. every unused fetch is waste
  4. one request per link, times the list
  5. code is safe to warm, data is not

basics

~20 s

Common triggers are a link entering the viewport, idle time after load, and intent signals such as hover, focus or touch start. Each fetch that is never clicked still costs bandwidth, an origin request and memory.

solid answer

~50 s

Prefetching fetches a route's payload before the user asks for it, so the click has nothing left to wait for. Frameworks trigger it in a few ways: when a link scrolls into the viewport, during browser idle time after the page settles, or on an **intent** signal — pointer hover, keyboard focus, or the touch that precedes a tap. Intent fires later and is far more accurate; viewport fires early and guesses more. The cost is everything fetched and never used: bytes on a metered connection, one origin or CDN request per link, and memory holding payloads that go stale. A list of two hundred links with eager viewport prefetching can turn one page view into two hundred requests, which is why most frameworks let you set the trigger per link and skip data while still warming code.

go deeper

for a junior

Know the idea: the framework can fetch a link's route ahead of the click so the move feels instant, and it can be switched off for a link that should not be fetched early.

for a middle

Be able to list the triggers — eager, viewport, idle, intent, never — and say what each one trades. Name the two halves of a payload and why warming code is safer than warming data.

for a senior

Show you count the requests a policy generates on a real surface, know the wasted-fetch ratio, and that speculative fetches hit the origin hardest exactly when traffic spikes.

for a principal

Argue the budget: whose bandwidth and whose capacity the speed is bought with, what the ceiling is under load, and how a default that is right for a nav bar becomes an incident on a feed.

**Prefetching** is speculative work: the client fetches the payload for a route the user has not asked for, betting that they will. When the bet pays off the navigation is instant, because the click has nothing left to wait for. When it does not, the fetch was pure waste — and the waste is not free for the user or for the origin. ## The triggers, from earliest to latest - **Eager / on render** — the payload is fetched as soon as the link exists. Fastest, most wasteful; sensible only for a handful of links you are confident about, such as the next step of a wizard. - **In viewport** — an intersection observer fires when the link scrolls into view. Reasonable on a short navigation bar; badly behaved on an infinite feed, where scrolling past content triggers a fetch per link. - **On idle** — the fetch is queued and runs when the main thread and network are quiet, so it does not compete with the initial load. Often combined with one of the other triggers as a scheduling rule rather than a trigger of its own. - **On intent** — pointer hover, keyboard focus, or `touchstart`/`pointerdown` before the tap completes. This buys only a few hundred milliseconds, but those milliseconds cover most of the round trip, and the hit rate is far higher than viewport. - **Never** — an explicit opt-out for links whose target is expensive, personalised, or rarely taken. ## What speculation actually costs | Cost | Who pays | When it bites | |---|---|---| | Bytes downloaded and discarded | the user, on a metered or slow connection | long lists, viewport trigger | | One request per link | your origin or CDN | a traffic spike multiplies it | | Bandwidth contention | the current page | prefetch competing with real content | | Memory holding payloads | the tab | many payloads held for a long window | | Staleness | correctness | the payload ages between fetch and click | The number that matters is the **wasted-fetch ratio**: prefetched payloads that were never navigated to, divided by all prefetches. A viewport trigger on a long feed can run well past ninety percent waste. An intent trigger on a navigation menu can be under half. Neither number is knowable from first principles — you measure it on your own surfaces. The origin side is the one teams forget. Prefetching turns one page view into many payload requests, all of them real server work if the route is not prerendered. Under a traffic spike that multiplier lands on the origin at the worst possible moment. Prefetching that a CDN absorbs is cheap; prefetching that reaches a dynamic route handler is not. ## Code, data, or both A payload has two halves and they have different costs. Fetching the **code** chunk for a route is idempotent, cacheable, small once cached, and useful forever. Fetching the **data** is per-user, often uncacheable, ages immediately, and is real server work. Several frameworks let you prefetch code only, which captures a large share of the latency win at a fraction of the cost and none of the staleness risk. It is the safe default for anything personalised. ## Being a good citizen - Respect a request to save data: browsers can send a `Save-Data` request hint, and connection information is readable from the platform; skipping prefetch on slow or metered connections is a one-line policy that users on those connections feel. - Cap concurrency, and schedule prefetches at low priority so they cannot delay the content the user is actually looking at. - Cancel prefetches for links that have scrolled away before they finish. - Prefetch only safe, side-effect-free targets. A URL that changes state must never be speculatively fetched — the speculation performs the action. ## Where frameworks differ Some default to viewport prefetching for in-app links and let you opt out per link; others prefetch nothing until you ask; others prefetch code eagerly and data only on intent. Some route the fetch through the same layer that serves navigations, so a prefetch warms exactly what the click will need; others use a browser-level hint instead, which is cheaper to implement but gives the framework less control over what is kept. Read the default your framework ships, then decide per surface rather than accepting it everywhere.

  • Why is hover or touch-start prefetching often the better default than viewport prefetching?
    Because it fires on a signal of intent rather than of visibility. A user hovering a link takes it far more often than a user scrolling past it, so the wasted-fetch ratio drops sharply. The latency win is smaller — a few hundred milliseconds instead of seconds — but that window covers most of a round trip, and on a long list it is the difference between a handful of requests and hundreds.
  • Why should you never prefetch a URL that has side effects?
    Because prefetching is indistinguishable from navigating for the server: the request arrives and the handler runs. A link that marks something read, consumes a token or triggers a job will do so as soon as the link is rendered or hovered, with no click. Speculative fetching is safe only for idempotent, side-effect-free targets.
  • How would you measure whether prefetching is helping?
    Pair two numbers. On the client, the navigation latency distribution, split by whether the payload was already in hand. On the fetch side, the wasted-fetch ratio — prefetched but never navigated to. A big latency win with a modest waste ratio justifies the policy; a small win with high waste means you are buying milliseconds with other people's bandwidth.

Prefetching is a waiter who starts cooking your usual as soon as you sit down. If you order it the food arrives instantly; if you order something else, the kitchen did the work and threw it away.

saying these in an interview costs you the question

  • Treats prefetching as free because the user did not wait for it.
  • Turns on viewport prefetching for an infinite feed without counting the requests.
  • Cannot name a trigger other than hover.
  • Prefetches a link that changes server state.
  • Ignores metered and slow connections when choosing a default.
  • Thinks prefetching removes the server work rather than moving it earlier.