URL → component: never direct, always via the tree
A URL like /users/42/posts never reaches a component directly. TanStack Router
turns file conventions (or createRoute() calls) into a parent-child
route tree, flattens it into a ranked candidate list, and
matches the URL segment-by-segment. The matched node's ancestor chain
becomes the rendered route-context stack: [Root, Users, UserDetail, UserPosts].
| stage | input | output |
|---|---|---|
1 · compile |
file convention: users.$id.posts.tsx |
tree node {'{ name:"UserPosts", path:"/users/$id/posts", parent:"UserDetail" }'} |
2 · linearize |
the parent-child tree | flat array of full paths, each carrying its ancestor chain |
3 · rank |
flat array | sorted by specificity — static(0) > $param(1) > $splat(2); longer beats shorter on ties |
4 · match |
URL /users/42/posts |
first ranked candidate whose segments all match → {'{ route, params:{id:"42"} }'} |
1 · the compiler you write (edit me)
2 · Babel compiles JSX → element tree
<App/> becomes React.createElement(App). Each render re-runs
matchUrl(CANDIDATES, url) against the pre-built, pre-ranked candidate list —
the same linearize-once / match-many shape TanStack uses at runtime.
// (hit "compile & render" to see Babel's output)
3 · live React (the matching pipeline, proven)
Type a URL (or click a quick button). The gold-check drives the
input directly: /users/42/posts → asserts UserPosts +
id=42, then /settings → asserts Settings. That proves
ranking put the right candidate first and $id was sliced into params.
tree nodes: — · gold: 6 nodes → /users/42/posts→UserPosts{id:42} → /settings→Settings
intent → pattern
| intent | pattern | why |
|---|---|---|
| static beats dynamic | /users/new before /users/$id |
rank 0 < rank 1 — static segment wins the same-length tie |
| capture one segment | /users/$id → params.id |
$ matches exactly until the next / |
| capture the rest | /files/$ → params._splat |
trailing $ is a splat/wildcard, consumes everything left |
| resolve ambiguity | params: { priority: 10, parse } |
higher priority candidate tried first; parse()=false falls through |
| build the context stack | matched node .chain → [Root, …, leaf] |
each ancestor's layout/outlet wraps the next — the route-context chain |