skip to content

When a client router handles an in-app link click, what does it fetch instead of a full HTML document?

level: juniorimportance: must knowfreq 72%

answer

  1. no new document is parsed
  2. transfer the difference, not the page
  3. diff the segment chain, request the tail
  4. data plus missing code chunks
  5. one URL, two possible response shapes

basics

~20 s

It fetches a payload for only the route segments that differ between the current URL and the target — their data and, if missing, their code — then swaps those segments into the page that is already running.

solid answer

~40 s

A document load makes the browser throw the page away: new HTML, every referenced asset re-evaluated, the client bundle re-executed from zero, all in-memory state gone. A client navigation keeps the running page and asks only for the **route payload** of the target URL — typically the data for the segments that change, plus their code chunks if they were not already downloaded. The router compares the segment chain it is currently rendering with the chain the target URL resolves to, and requests only the differing levels; the shared levels above the divergence point stay mounted. Because the answer depends on what the client already holds, the request usually carries that context, so the same URL can return full HTML to a cold browser and a partial payload to an in-app move.

go deeper

for a junior

Remember the one-line version: a client navigation fetches the changed part of the route, not a new document, and the page never restarts.

for a middle

Be able to explain the diff — current segment chain against target segment chain — and name what the payload contains: data for the changed segments plus any code chunk not already downloaded.

for a senior

Show that you watch payload size per route the way you watch bundle size, and that you know one URL has two response shapes, so caches in front of the origin must key on the client signal.

for a principal

Frame it as a transfer-cost budget: what the shell keeps mounted, what every move pays for, and which of those costs you are willing to move to the server or to a speculative fetch.

A **client navigation** is a move from one route to another that the framework handles inside the page that is already running: the URL in the address bar changes and the view changes, but the browser never loads a new document. Knowing exactly what crosses the network on that move is the foundation of everything else in client routing — prefetching, scroll and focus handling, and the cases where the router has to give up and let the browser do a real load. ## What a document load actually costs When the browser loads a document it does all of this: - parses a fresh HTML document and discards the current one entirely; - re-evaluates every stylesheet, script and font the new document references (cached bytes still cost parse and execute time); - re-executes the client bundle from zero, rebuilding the component tree and every store; - loses all in-memory state: open menus, scroll containers, unsent form input, live connections; - re-runs the server work for the whole page, not just the part that differs. On a content site that is often acceptable. On an application shell with a heavy sidebar, a session, and a data layer, paying it on every link click is the difference between a move that feels instant and one that blinks. ## What the client asks for instead The router intercepts the click, works out which route the target URL resolves to, and requests that route's **payload** rather than a document. The payload is usually two things: 1. **Data** — the serialised result of the server-side work for the segments that change: what a loader returned, or a serialised render of those segments. 2. **Code** — the JavaScript chunks for any segment the client does not already have. A route the user has visited before usually needs no new code at all. The HTML shell, the shared styles and the already-executing bundle are not part of it. ## Only the changed segments A URL in a file-based router resolves to a **chain** of segments — a root shell, then progressively more specific levels, ending at the leaf that renders the page. Moving from one URL to another inside the same application usually changes only the tail of that chain. The router diffs the two chains and asks for the levels below the point where they diverge; the levels above it stay mounted and are not re-requested. Move between two sibling detail pages and the payload can be a few kilobytes of data for one segment. Move across the whole tree and it is most of the chain. | | Document load | Client navigation | |---|---|---| | What is requested | a full HTML document plus its assets | the payload for the differing segments | | Client bundle | re-parsed and re-executed | keeps running | | Shared shell | rebuilt from scratch | stays mounted | | In-memory state | discarded | preserved | | Typical size | tens to hundreds of kilobytes | often a few kilobytes | ## The request carries more than the URL Because the correct response depends on what the client already holds, the request has to say so — through a request header, a query parameter, or a distinct path that the build or server recognises. That means one URL legitimately has two very different responses: full HTML for a cold browser, a partial payload for an in-app move. Anything caching in between has to be told the response varies on that signal (`Vary`, or a different cache key). Get it wrong and a shared cache eventually hands an HTML document to a client that expected a payload, or the reverse, and navigation breaks in a way that only reproduces behind the cache. ## Where meta-frameworks differ They do not all serialise the same thing. Some send structured data and let the client render the segments it already has code for. Some stream server-rendered markup for the changed part and let the client patch it into the tree, which keeps server-only work on the server at the cost of a larger payload. Some send only code and let the client fetch data itself in a second request, which is simpler but adds a round trip. The shape differs; the principle — transfer the difference, not the document — does not. ## What to do with this in practice - Watch the network panel during a navigation: you should see one small payload request, not a document plus assets. - A shared level that refetches on every move is visible right there as payload that never shrinks — usually a sign the diff is not finding the shared prefix. - Payload size per route is a number worth tracking, the same way bundle size is; it is what the user waits for on every click. - If the payload request fails, the router has to decide whether it already committed the URL — frameworks differ, and the recovery a user sees differs with it.

  • Why can the same URL return a full HTML document to one request and a small payload to another?
    Because the client tells the server what it already has — via a request header, a query parameter or a distinct path — so the server can answer with only the differing segments. A cold browser sends no such signal and gets the document. Any shared cache in front of the origin must key on that signal, or it will eventually serve the wrong shape.
  • What happens if the payload request for a navigation fails halfway?
    The router has to choose between committing the URL optimistically and rolling back. Meta-frameworks differ: some update the address bar immediately and render an error boundary for the failed segment, others hold the current view until the payload arrives and surface a retry. Either way the running page survives, which is exactly what a document load could not offer.
  • Does a client navigation re-run the server work for the whole page?
    No — only for the segments below the divergence point of the two chains. Levels shared by both URLs are not re-requested and their server work is not repeated, which is why a deep shared shell is cheap to keep. Whether a shared level can opt into re-running anyway is a framework-specific choice.

saying these in an interview costs you the question

  • Says the browser downloads and parses the whole HTML page again on every link click.
  • Claims a client navigation and a document load transfer the same bytes.
  • Thinks the client bundle is re-downloaded and re-executed on each move.
  • Assumes a navigation payload is always HTML rather than possibly serialised data.
  • Believes the target URL alone determines the response, ignoring what the client already holds.