How do you decide how many layout levels a deeply nested route hierarchy needs and what each level owns?
answer
- levels are a budget, not free structure
- persistence, region, scope
- segments and levels are independent
- a pass-through level buys nothing
- shallowest tree with one home each
basics
~20 sAdd a level only when something at that depth must persist across navigations below it, own a region of the screen, or scope an access check or data load. URL segments do not by themselves justify levels.
solid answer
~50 sTreat levels as a budget. Each one buys three things and nothing else: **persistence** (chrome or state that must survive navigations below it), **region ownership** (the area a failure or pending display at that level is allowed to replace), and **scope** (a check or a data load that applies to everything beneath). If a candidate level buys none of those, it is pass-through indirection. Levels and URL segments are also independent — a pathless level adds nesting with no segment, and a segment can exist with no level of its own — so do not mirror the URL out of habit. Under-nesting duplicates chrome, remounts it and lets one failure blank the whole screen; over-nesting produces near-empty levels, hides where rendering happens, and scatters work at levels that then rarely re-run. Aim for the shallowest tree where every persistent thing has one home, and write down which level owns which region.
go deeper
Notice the shape of the app you work in: some parts of the screen belong to an outer level and some to the screen you are editing. Ask which level owns a piece before adding it.
Justify a level out loud with what it buys — something that persists, a region it owns, a rule it scopes — and know that nesting and URL segments are separate decisions.
Place failure and pending regions deliberately so a broken section leaves the rest usable, and make sure a persistent level refreshes by reacting to its parameters rather than by being rebuilt.
Own the tree as a contract: write down which level owns which region and state, gate new levels on what they buy, and account for the invisible cost of moving one later.
## What a level actually buys A nesting level is not free structure; it is a commitment every future contributor reads. It is worth adding when it buys at least one of three things: 1. **Persistence.** Chrome, state, a scroll container or a subscription that must survive every navigation below it. This is the strongest reason, because it cannot be had any other way. 2. **Region ownership.** A failure or pending display declared at a level replaces that level's outlet region. Choosing the level therefore chooses the **blast radius** of a failure: how much of the screen the user loses, and how much remains usable. 3. **Scope.** One place to express an access check or a data load that applies to everything beneath, instead of repeating it per screen. If a proposed level buys none of those, it is a pass-through: a component whose entire job is to render an outlet. ## Three tests before adding one 1. **The retention test.** Name one thing at this depth that must still be there after the user navigates below it. No answer means no level. 2. **The blast-radius test.** If something at this depth fails or is not ready, what should the user still be able to use? If the answer differs from the level above, this depth needs its own region. 3. **The scope test.** Is there a rule — an entitlement check, a load, a context value — that applies to everything below and nothing beside? If yes, the level gives it a single home. ## Levels and segments are independent decisions The most common structural mistake is to mirror the URL: one segment, one level, automatically. They answer different questions. | you want | use | effect on the URL | |---|---|---| | screens to share an address prefix | a parent route with a path fragment | adds a segment | | screens to share chrome or state, addresses unchanged | a pathless level | none | | a segment with nothing to persist at that depth | a deeper path fragment on one entry, no extra level | adds a segment | | a section's base address to show something | an index child | none | Published addresses are a long-lived contract; the component tree is refactorable. Letting the URL dictate the tree freezes the cheap decision to the expensive one. ## Over-nesting and under-nesting | | under-nested | over-nested | |---|---|---| | chrome | duplicated in several screens | spread thin across levels that barely render | | state | rebuilt on navigation, so it resets | technically preserved, but nobody can find its owner | | failures | one failure blanks the whole screen | tiny isolated regions, and a fragmented screen while several are pending | | reading the code | one file shows everything, with repetition | "what renders here?" takes a walk down several near-empty levels | | work placement | repeated per screen | placed at a level that is kept, so it quietly never re-runs | The last row of the over-nested column is the failure mode teams underestimate. A level that persists is a tempting place for a load, and precisely because it persists, that load runs once and the numbers on screen drift. Every level you add is a place where that mistake becomes available. ## Leading the decision - **Name the owner of each region in writing.** A short table — level, chrome it renders, state it holds, region its failure replaces — prevents the slow drift where two levels both half-own the header. - **Make the review rule explicit**: a new level must state which of the three things it buys. This is far cheaper than removing a level later. - **Budget for movement.** Moving a level changes what resets for every descendant, and that change is invisible in a diff. If a level might move, do not let much state accumulate there yet. - **Watch for chrome that becomes conditional on the child.** When a level starts asking "which child is showing?" to decide what to render, the chrome is in the wrong place — either push that part down into the children, or split the level. - **Prefer the shallowest tree that gives each persistent thing exactly one home.** Depth is easy to add later when a real need appears, and hard to remove once state, checks and loads have settled into it. ## A worked judgment A product with a top shell, a workspace switcher, a section rail and per-record tabs *looks* like four levels. Interrogate each: the top shell persists and owns the whole-app failure region, so it stays. The workspace level holds membership and entitlement data that everything below reads, so it stays. The section rail renders chrome that all its children show and keeps its own scroll, so it stays. The per-record tabs level is worth a level only if something must persist across tab changes for one record — a loaded record, a draft, a scroll position. If each tab loads independently and shares nothing, the tabs are markup in the record screen, not a level. Four candidates, three or four levels, and the difference is decided by evidence rather than by the shape of the URL.
- How do you decide which level owns a section's data?The level whose lifetime matches the data's lifetime and whose descendants all need it. Put it any higher and it outlives its relevance and goes stale; any lower and each child loads it again. Whichever level owns it must also react to the parameters it depends on, since a kept level never remounts to refresh itself.
- What makes moving a layout level expensive after the fact?Its position defines what persists and what resets for everything below it. Moving it silently changes which state survives a navigation, when loads re-run, and how much of the screen a failure replaces — none of which is visible in the diff. Accumulated state at that level makes the move costlier still.
- How would you tell whether an existing level is pure pass-through?Read what it renders and holds: if it renders only an outlet, holds no state, declares no failure or pending region and scopes no check or load, it buys nothing. Merge it into its parent or its children, and expect a route table and a few imports to change, nothing more.
saying these in an interview costs you the question
- Adds a level for every URL segment as a matter of course
- Keeps levels that render nothing but an outlet
- Puts a data load at a persistent level and expects it to re-run
- Declares one failure region for the entire application
- Lets a level branch on which child matched to choose its chrome
- Treats route nesting as settled once, never revisited