Lesson 1 — How flat filenames build nested routing
You know Next.js: a folder is a path segment, and layout.tsx wraps its children.
TanStack Router flips this: filenames are the path, and dots create nesting.
The trick is learning to read a flat file tree and see the route tree in your head.
In Next.js:
In TanStack Router, the same idea is encoded in flat filenames with dots:
home.dashboard.tsx is a child of home.tsx. No folder needed.
In Next.js, layouts get {children} automatically. In TanStack Router, you explicitly choose where child routes render using <Outlet />.
// routes/home.tsx — a layout route
import { Outlet, createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/home')({
component: HomeLayout,
})
function HomeLayout() {
return (
<div className="feed-layout">
<nav>Feed • Explore • Profile</nav>
<Outlet /> {/* ← child route renders here */}
</div>
)
}
When you visit /home/42, TanStack Router finds both home.tsx and home.$postId.tsx, renders the layout, and plugs the child into the outlet:
| URL | Component Tree |
|---|---|
/home | <HomeLayout> |
/home/42 | <HomeLayout><PostDetail postId="42" /></HomeLayout> |
Let's model a typical mobile social app: tabs at the bottom, detail screens that slide in, and settings tucked behind a layout.
_app.tsx is a pathless layout. It wraps children but does not add a segment to the URL. The URL is /explore, not /_app/explore. We'll cover this fully in Lesson 2.
| URL | Rendered Tree |
|---|---|
/ | <Root><AppLayout><HomeFeed /></AppLayout></Root> |
/explore | <Root><AppLayout><Explore /></AppLayout></Root> |
/posts/42 | <Root><AppLayout><PostDetail postId="42" /></AppLayout></Root> |
/posts/42/comments | <Root><AppLayout><PostDetail postId="42"><Comments /></PostDetail></AppLayout></Root> |
Notice how /posts/42 renders <PostDetail /> inside the app layout.
But /posts/42/comments renders <Comments /> inside <PostDetail /> — which is itself inside <AppLayout />. Three levels of nesting, entirely from filenames.
routes/shop.items.$itemId.tsx match?$ marks a dynamic segment. shop.items creates two static segments. The file maps to /shop/items/:itemId._app.settings.tsx?_app hides the layout_app.tsx (the pathless layout)__root.tsx only_app.tsx is still a layout route. Any file starting with _app. is its child.home.tsx has no <Outlet />, what happens at /home/dashboard?<Dashboard /> renders<HomeLayout /> renders, but child content is losthome.tsx, but without an <Outlet /> there is nowhere to place the child. The child simply doesn't appear.