The one idea
The filesystem IS the route tree: files under
src/routes/ ARE routes, and a plugin compiles them into
routeTree.gen.ts — the generated file that powers the
type-safety. $ = a param, _ prefix = a pathless layout,
and a layout route wraps its children through an <Outlet/>.
There is no hand-written route config to keep in sync. You author route files; the
Vite plugin (@tanstack/router-plugin/vite) watches the directory and
regenerates src/routeTree.gen.ts on every save. That generated file is
imported by createRouter({ routeTree }) — and its inferred types flow
out to every <Link>, navigate, useParams,
and loader. It is generated, not hand-written — yet you
commit it (it is app source, not a throwaway artifact).
The codegen step — files become a typed route tree
This is the half that makes TanStack Router different from a plain file-mapper: the
files are compiled, not just read. Click run codegen to
watch the plugin walk src/routes/ and emit
src/routeTree.gen.ts, which the typed router then consumes.
// you write route files
__root.tsx
index.tsx
posts.tsx
posts/$postId.tsx
_layout/route-a.tsx
...
// tanstackRouter({ target:'react',
// autoCodeSplitting:true })
watches src/routes/
emits routeTree.gen.ts
// GENERATED — do not hand-edit
rootRoute
.addChildren([...])
// + FileRoutesByPath type
// (the type-safety is born here)
// routeTree feeds the router; // its types flow to Link, // navigate, useParams, loader
The route-tree visualizer — click a file, see its route + nesting
Left: the curated src/routes/ tree (mirrors the official conventions).
Right: type a URL (or click a sample) — the resolver shows which file matches, the
params it receives, and the component chain it renders inside. Clicking a file in the
tree jumps the other way. _layout is a pathless route: it wraps its
children but adds nothing to the URL.
$param
$ splat
_pathless
The component chain — where the matched route nests
A layout route renders an <Outlet/> where its child goes. So a
matched URL renders as a nested chain from <Root>
down to the leaf. Pathless layouts (the dashed _layout below) sit in the
chain but contribute no URL segment — that is the entire point of the
_ prefix.
Resolve a URL above to see its chain render here.
File convention → meaning → example URL
Two ways to make a layout route: a co-located file
(posts.tsx next to a posts/ dir) or a
route.tsx inside the directory (settings/route.tsx). Both
wrap their directory's children. The _ prefix is different: it makes the
route pathless (a layout with no URL segment of its own).
| File | Kind | Route path / URL | Read as |
|---|---|---|---|
| __root.tsx | root route | — (no URL; wraps everything) | createRootRoute() |
| index.tsx | index | / (the root index) | path '/' |
| about.tsx | static | /about | path 'about' |
| posts.tsx | layout route (co-located file) | /posts + wraps posts/* | path 'posts' + <Outlet/> |
| posts/index.tsx | index (of dir) | /posts (exact) | path '/' under posts |
| posts/$postId.tsx | dynamic segment | /posts/$postId | params.postId (string) |
| settings/route.tsx | layout route (dir's route.tsx) | /settings + wraps settings/* | path 'settings' + <Outlet/> |
| settings/profile.tsx | static nested | /settings/profile | path 'profile' under settings |
| _layout.tsx | pathless layout (_ prefix) | — (no URL segment) | id: '_layout', no path |
| _layout/route-a.tsx | static, wrapped by pathless | /route-a (no /_layout in URL) | path 'route-a' |
| files/$.tsx | splat / catch-all | /files/$ (any depth) | params._splat (string) |
| posts_/$postId/edit.tsx | non-nested (trailing _) | /posts/$postId/edit — NOT wrapped by Posts | detached from parent layout |
A trailing _ on a name (posts_ dir, or posts. flat
→ posts_.) detaches the route from the parent's layout component while
keeping the URL nested. Flat routes use . instead of /:
posts.$postId.tsx ≡ posts/$postId.tsx.