skip to content

In a nested layout chain, when a navigation changes only the deepest URL segment, which levels re-run and which are reused?

level: middleimportance: must knowfreq 72%

answer

  1. a diff, not a reload
  2. the comparison runs level by level
  3. unchanged above, rebuilt below
  4. identity is module plus resolved parameters
  5. a reused level skips its data step

basics

~20 s

The router diffs the previously matched chain against the newly matched one. Levels that match identically stay mounted and their data step is not called again; from the first level that differs downward, modules re-render and their data steps run again.

solid answer

~40 s

On a client navigation the router resolves the new URL to a chain and compares it, level by level, with the chain already on screen. A level counts as unchanged when the **same module** is matched with the **same resolved parameters**. Those levels are kept mounted, their markup stays in place, and their data step is not called again. From the first level that differs - a different module, or the same module with a different dynamic parameter - everything downward is re-rendered and re-resolved. That is deliberate: it is what makes moving between sibling routes cheap. It is also the classic surprise, because a value a shared level resolved once does not refresh merely because the user moved between its children.

code

pseudocode · 11 lines
pseudocode
match("/teams/42/members") -> [root, teams, team(id=42), members]
match("/teams/42/settings") -> [root, teams, team(id=42), settings]

diff(old, new):
  root      same module, same params -> reuse (mounted, data step skipped)
  teams     same module, same params -> reuse
  team(42)  same module, same params -> reuse
  members -> settings: first difference -> re-render + run data step here and below

match("/teams/77/members"):
  team(42) vs team(77): same module, different param -> first difference is here

go deeper

for a junior

Remember the shape: the levels above the change stay put and the levels from the change downward are rebuilt. That is why the sidebar does not flash when you click a tab.

for a middle

Be able to state the identity test out loud - same module and same resolved parameters - and work through a case where a parent's parameter changes even though the deepest segment name does not.

for a senior

Diagnose the stale-shared-level report without reaching for a reload: name which level was reused, say why that was correct behaviour, and pick the remedy that matches the freshness the value actually needs.

for a principal

The diff is a caching policy expressed as a file tree. Decide deliberately what sits above the common navigations, because that placement fixes both the cost profile and the freshness profile of everything below.

## The router diffs chains, not URLs A client navigation in a meta-framework is not a page load. The router matches the target path to a chain of levels and then compares that chain, position by position, against the chain currently mounted. The comparison is structural, not textual: two URLs that look very different can share most of their chain, and two URLs that differ by one character can invalidate almost all of it. A level is treated as **unchanged** when both of these hold: - the **same route module** is matched at that position in the chain, and - the **parameters resolved for that level** are the same values as before. Levels that satisfy both are kept mounted. Their rendered output stays on screen, their component state stays alive, and their data step is not called again. From the **first level that fails the test**, that level and everything beneath it is re-rendered, and the data steps of those levels run again. ## Worked through | Navigation | Reused | Re-rendered and re-resolved | |---|---|---| | `/teams/42/members` to `/teams/42/settings` | root, teams, team(42) | settings | | `/teams/42/members` to `/teams/77/members` | root, teams | team(77) and everything below | | `/teams/42/members` to `/billing` | root | billing and below | | Full document load of the same URL | nothing | the whole chain | The second row is the one people get wrong. The deepest segment name did not change, yet a level above it was matched with a different parameter value, so that level is not the level it was - it and its whole subtree are rebuilt. ## Why the model works this way Re-running only the changed part of the chain is the entire economic argument for nesting. A dashboard shell that costs a database round trip and a sizeable chunk of markup should not be paid for again because the user clicked a tab inside it. Reuse also keeps the shell's state alive across the move, which is what makes the navigation feel like moving inside an application rather than loading a new page. The transport follows the same shape: since the router knows which levels changed, the client only needs data and markup for those levels. The consequence to remember is that **less work happening is the feature, not a bug** - the reuse is exactly what you asked for by putting the work above. ## The classic surprise A level near the top resolves something that changes over time - an unread count, a quota, a permissions snapshot. The user clicks around inside that subtree for ten minutes and the value never moves. Nothing is broken: that level kept matching identically, so it was never re-run. The useful mental model is that **a shared level is refreshed by leaving and re-entering the subtree, not by moving within it**. The generic remedies, in rough order of bluntness: 1. **Ask the router to re-resolve the current URL.** Most meta-frameworks expose a way to invalidate the matched chain and run its data steps again; this refreshes the shared level along with everything else. 2. **Move the read down** to the level whose parameters it actually depends on, so it is invalidated naturally by the navigations that matter. 3. **Make the value client-owned** - subscribed, polled or pushed - so its freshness is decoupled from route matching altogether. 4. **Split the level**: keep the cheap, stable chrome above and put the volatile part in its own level lower down. ## Edges worth knowing - **Query-string-only changes.** Frameworks genuinely differ here. Some treat search parameters as outside the level identity, so nothing re-runs and the page must react to them itself; others invalidate the level that reads them. Assume nothing and check the framework you are on. - **A full document load re-runs everything**, which is why a bug of this class so often disappears on refresh and returns on the next client navigation. - **Data freshness is not the same as matching.** A mounted level's data can be stale because the underlying record changed on the server; no amount of route diffing notices that, because nothing about the chain changed. - **Reuse keeps state, not just markup.** The mounted level's component state, timers and subscriptions survive with it.

  • A shared level shows a stale count as the user clicks around beneath it. What are the options?
    Ask the router to re-resolve the current URL, which runs the chain's data steps again; move the read down to a level the navigations actually invalidate; make the value client-owned so it refreshes on its own schedule; or split the level so the volatile part sits lower and the stable chrome stays shared. Which one fits depends on how fresh the value has to be.
  • Does changing only the query string re-run the chain?
    Meta-frameworks differ. Some exclude search parameters from level identity, so no level is invalidated and the page has to react to the new values itself; others treat a level that reads them as changed and re-run it. Never assume - it is one of the first behaviours to confirm on an unfamiliar framework.
  • Why does a parameter change on a parent invalidate the levels below it?
    Because those levels were resolved in the context of the old value: their data steps were called with it and their rendered output assumes it. The router cannot tell which of them actually depended on it, so it rebuilds the subtree from the first changed level down rather than guessing.

saying these in an interview costs you the question

  • Thinks every level of the chain re-runs its data step on every navigation
  • Assumes a parent level refreshes because one of its children was replaced
  • Believes a parent is reused even when its own dynamic parameter changed
  • Says the router compares URL strings rather than matched levels and parameters
  • Expects a mounted level to notice that its data changed on the server