a match is a chain, not a single component
When TanStack Router matches /admin/users/42, it does not render one component.
It resolves the leaf (UserDetail) and walks its ancestor chain:
[Root, Admin, Users, UserDetail]. Each ancestor renders as a layout
with an <Outlet/> placeholder, and the next route in the chain renders inside it —
nested rendering. At the same time, each route's beforeLoad returns a context object that is
merged down the chain, so the leaf receives the union of every ancestor's context.
| route | beforeLoad provides | component renders | inherited at this level |
|---|---|---|---|
/ Root |
{'{ theme, user }'} |
header + <Outlet/> |
{'{ theme, user }'} |
/admin |
{'{ adminMode }'} |
sidebar + <Outlet/> |
{'{ theme, user, adminMode }'} |
/admin/users |
{'{ userFilter }'} |
UserList | {'{ theme, user, adminMode, userFilter }'} |
/admin/users/$id |
{'{ detailTab }'} |
UserDetail | {'{ theme, user, adminMode, userFilter, detailTab }'} |
The chain only includes ancestors of the matched leaf. Navigate to /admin and the chain collapses
to [Root, Admin] — Users and UserDetail never mount, so userFilter/detailTab
are absent from context.
1 · the nested-context visualizer (edit me)
2 · Babel compiles JSX → element tree
<NestedContextDemo/> becomes React.createElement(NestedContextDemo).
Clicking a route calls setActivePath → re-render → findLeaf +
buildChain + accumulateContext rebuild the chain and merged context from scratch.
// (hit "compile & render" to see Babel's output)
3 · live React (nested chain + context accumulation, proven)
Click any route in the tree. The gold-check drives it directly: default
/admin/users/42 → asserts chain [Root, Admin, Users, UserDetail] with all 5 context
keys, then CLICKS /admin → asserts the chain collapses to [Root, Admin] and the
context loses userFilter/detailTab. That proves matching, chain construction, and
the top-down context merge all work.
routes: — · gold: /admin/users/42→[Root,Admin,Users,UserDetail]{5 keys} → /admin→[Root,Admin]{3 keys}
intent → pattern
| intent | pattern | why |
|---|---|---|
| wrap child routes in a layout | parent component renders <Outlet/> |
the Outlet is where the matched child renders — that is nested rendering |
| provide data to a route + descendants | beforeLoad: ({'{ context }'}) => ({'{ ...context, ... }}') |
spread inherited context then add your keys — returns the merged object |
| read parent data in a child loader | loader: ({'{ context }'}) => fetchUsers(context.adminMode) |
context is already merged by the time the loader runs |
| opt OUT of nesting | name the file _path.tsx (pathless layout) or un-nest via _ |
renders its own component without the parent layout / Outlet |
| access the accumulated context | Route.useMatch().context / useLoaderData() |
type is the union of every ancestor's beforeLoad return type |