What is structurally wrong when a nested-route app's shared sidebar collapses, loses its scroll and reloads its data on every section switch?
answer
- three losses, one instance
- state, scroll and a refetch together
- find the deepest shared level
- hoist the chrome above the divergence
- an identity signal forces a rebuild
basics
~20 sThe chrome is not on a level both URLs share, so it is rebuilt on each navigation — usually because each screen renders its own copy, or because its level sits at or below the point where the two matches diverge.
solid answer
~50 sThose three symptoms together — reset chrome state, reset scroll, a repeated load — are the signature of a **remount**, not of slow data. So find the divergence point: list the matched chain for both URLs and locate the deepest level they share. If the component holding the sidebar is not at or above that level, it cannot survive, because everything below the divergence point is torn down. The usual causes are chrome rendered inside each screen, chrome duplicated under each section's entry instead of hoisted above them, a path-derived identity attached to that level, or a wrapper above it recreated per navigation. The fix is to hoist the chrome into one parent level common to both URLs — a pathless level if they share no segment — put the outlet directly inside it, and move the section's state and data ownership up to it.
code
pseudocode · 8 lines# wrong: each screen renders the chrome itself -> new copy per navigation
route "/inbox" -> sidebar + inbox list
route "/archive" -> sidebar + archive list
# right: the chrome is one shared level; children fill its outlet
route "/" -> sidebar + outlet
route "inbox" -> inbox list
route "archive" -> archive listgo deeper
Learn the signal: state, scroll and a repeated request all lost at once means the component was recreated. Shared chrome belongs to a parent level, not to each screen.
Explain it with the matched chain: everything below the point where the two matches diverge is torn down, so a level that must persist has to sit at or above that point.
Diagnose before changing anything: prove the remount with instrumentation, compare both matched chains, then hoist ownership rather than restoring state after the fact.
Name the tradeoff you are buying. A persistent level gives retention and takes away the free refresh a remount provided, so decide per level and make that contract explicit for the team.
## What the symptom set tells you Read the three symptoms as one signal. A collapsed panel reopening is lost local state; a content area jumping to the top is a lost element reference; a repeated request is mount-time work running again. One instance cannot lose all three unless it is a **new instance**. So this is not a caching problem and not a rendering-performance problem — some level that you believed was shared is being created afresh on each navigation. ## The four structural causes 1. **The chrome lives inside each screen.** Every leaf renders the sidebar itself. Both screens look right in isolation, and each navigation destroys one copy and builds another. The tell is that the sidebar's markup appears in more than one screen's source. 2. **The chrome is a route level, but not a shared one.** It was declared under each section's entry rather than above them, so the two matches diverge *at* the chrome level. The tell is the matched chain: the deepest level the two URLs share is above the one holding the chrome. 3. **The chrome level is given a changing identity.** An identity value derived from the path or a parameter is attached to that level, which is an explicit request for a new instance whenever it changes. Routers and frameworks offer this on purpose — for a level that must reset per record — and it is easy to apply to a level that should persist. 4. **A recreated wrapper sits above it.** A per-navigation wrapper — something that wraps the outlet's content to animate or isolate it — makes its child a new instance on every transition, and the chrome inherits that fate if it is rendered inside. ## Diagnosing in order 1. **Prove the remount.** Record a line or increment a counter when the chrome level is created. Navigate. If the count rises, stop looking at data and network behaviour. 2. **Write down both matched chains.** For URL A and URL B, list the matched entry at each level and mark the deepest level they share. Anything that must persist has to live at or above that mark. 3. **Check the level for an identity signal.** If the persisting level carries an identity derived from the path or parameters, that alone explains it. 4. **Check what is above it.** Look for a wrapper that is recreated per navigation between the router and the chrome. 5. **Check for duplicates.** Search for the chrome's own markup; more than one call site is cause 1. ## The fix - **Hoist the chrome to one level both URLs match.** If the sections share a URL prefix, that is a parent route on the prefix. If they share no prefix, use a **pathless level** so grouping costs no segment. - **Put the outlet directly inside the chrome**, so the only thing that changes per section is the outlet's content. - **Move ownership upward with it**: the panel's expanded state, the scroll container, the section-wide data load, the subscription. Ownership is the whole point of the level. - **Remove the accidental identity signal** from that level, and move any per-navigation wrapper *inside* the outlet so it wraps only what genuinely changes. - **Re-run the retention test** afterwards: type into a throwaway field in the chrome, navigate, and confirm it is still there. ## Variants that look identical from the outside | what you observe | the mechanism | where to look | |---|---|---| | chrome resets on every navigation | remount of the chrome level | the matched chain and the level's identity | | chrome keeps state but its numbers are stale | the level is kept, so mount-time work never re-runs | the level's reaction to changed parameters | | chrome resets only when the record changes | the level's own dynamic segment changed *and* an identity was attached | that level's identity signal | | only the inner area flickers | the divergence point is where it should be; nothing to fix | nothing — this is the healthy shape | The second row is the mirror-image defect and worth naming out loud in an interview: hoisting state into a persistent level buys retention and takes away the automatic refresh a remount used to give you for free. Both cannot be had by accident; you choose. ## Why patching the symptom is the wrong answer The tempting fixes are to persist the panel state somewhere global and restore it after each rebuild, or to cache the request so the repeated load is cheap. Both leave the remount in place, so the next thing the chrome holds — a focus position, an open menu, a pending edit — breaks again, and the team learns a false rule that shared chrome cannot hold state. Restoring state is the right tool only when a remount is genuinely unavoidable. Finally, keep the judgment symmetric: a remount is sometimes exactly what you want. A detail level that must show nothing from the previous record is easier to reason about when it starts clean. The defect here is not that a remount happened; it is that it happened at a level whose purpose was to persist.
- When is remounting on navigation the behaviour you actually want?When a level must not carry anything over — a detail shell for a different record, a multi-step flow that has to start at step one, a screen whose validity depends on data it fetched at mount. There you either place it below the divergence point or attach a deliberate identity so each entity gets a clean instance.
- Where does the shared chrome go when the grouped screens share no URL segment?In a pathless level: a parent entry with a component and an outlet but no path fragment. It becomes part of every match in the group, so it is shared and persists, while the children's addresses stay exactly as they were.
- After hoisting the chrome, its counts are correct on a full load but never change afterwards. Why?Because it now persists: mount-time work runs once, and the refresh a remount used to provide is gone. Make the level react to what changed — re-run its load when the relevant parameter or an explicit refresh signal changes — rather than depending on being rebuilt.
saying these in an interview costs you the question
- Calls a remount on every navigation normal router behaviour
- Persists chrome state globally and restores it after each rebuild
- Blames slow data and adds caching to hide the repeated load
- Concludes shared chrome cannot hold local state
- Attaches a path-derived identity to the level that must persist
- Duplicates the chrome per screen and syncs the copies