TanStack Router 03 ⚛️ File-Based Routing — File convention & generation
The previous chapter created the root route and the layout. This chapter covers the file-based routing convention: the directory structure under src/routes/, the file naming rules that map a file to a URL, and the generated routeTree.gen.ts that connects the files to the router. The LFCS exam does not test TanStack Router, but the front-end engineering certification paths do. The critical detail is that the filename is the URL. A file named about.tsx in src/routes/ becomes the /about route. A file named users.$id.tsx becomes the /users/:id route with a $id parameter. A file named __root.tsx becomes the root layout. A file named index.tsx becomes the default child of its parent route. The dots and the dollar signs in the filename are not cosmetic—they are the routing convention.
Key point: The file path under src/routes/ determines the URL. The index.tsx is the default child (/). The about.tsx is the static route (/about). The users.$id.tsx is the dynamic route (/users/:id). The users.index.tsx is the index child of a parent (/users/). The users.tsx is the layout route for the /users path with an <Outlet />. The __root.tsx is the root layout. The $.tsx is the splat route (/*).
Why file-based routing requires a specific mental model
The path problem. The URL path is derived from the file path. The src/routes/about.tsx becomes /about. The src/routes/users/profile.tsx becomes /users/profile. The src/routes/users/$id.tsx becomes /users/:id because the $ prefix marks the segment as dynamic. The filename extension is stripped. The directory structure is preserved.
The index problem. The index.tsx file is the default child of its parent route. The src/routes/index.tsx becomes the / route. The src/routes/users/index.tsx becomes the /users/ route. The index file is the landing page for the parent’s path. Without it, the /users/ path has no component to render when the parent is a layout route.
The layout problem. A file without an index or a $ is a regular route. The src/routes/users.tsx is a layout route: it renders when the URL starts with /users, and its <Outlet /> renders the child routes. The src/routes/users.profile.tsx is the child route /users/profile. The users.tsx file and the users/ directory can coexist in older versions, but the flat naming convention (users.profile.tsx) avoids the ambiguity.
The dynamic segment problem. The $ prefix marks a segment as dynamic. The users.$id.tsx becomes /users/:id. The $ is the parameter name. The useParams hook returns the value. The users.$id.edit.tsx becomes /users/:id/edit. The dynamic segments can be nested.
The splat problem. The $.tsx is the splat route. It matches any URL that the other routes do not match. The $ without a name is the splat. The params["_splat"] contains the unmatched path. The splat is the catch-all, the 404 page.
The generation problem. The @tanstack/router-plugin reads the src/routes/ directory and generates the routeTree.gen.ts file. The file contains the route tree that the createRouter function uses. The file is regenerated on every file change when the dev server is running. The file should be ignored by the linter and formatter.
The trade-off. The file-based routing is more convenient for the large applications because the file structure is the route structure. The code-based routing is more explicit and is the correct choice for the applications that need the programmatic route construction. The file-based routing is the default for the TanStack Router starter.
a. The static routes and the index route
A static route is a file whose name matches the URL segment. The about.tsx becomes /about.
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: About,
})
function About() {
return <div>About page</div>
}
The createFileRoute('/about') declares the route path. The path must match the file location. The plugin uses the path to generate the route tree. A mismatch between the path and the file location causes a generation error.
The index.tsx is the default child. The src/routes/index.tsx becomes the / route.
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: Home,
})
function Home() {
return <div>Home page</div>
}
The src/routes/users/index.tsx becomes the /users/ route. The users/index.tsx is the default child of the users layout route.
b. The dynamic segments and the splat
The $ prefix marks a dynamic segment. The users.$id.tsx becomes /users/:id.
// src/routes/users.$id.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/users/$id')({
component: UserProfile,
})
function UserProfile() {
const { id } = Route.useParams()
return <div>User {id}</div>
}
The Route.useParams() returns the typed parameters. The id is typed as string. The createFileRoute('/users/$id') path is the exact file location. The $id in the path is the same as the $id in the filename.
The $.tsx is the splat route. It matches any URL that the other routes do not match.
// src/routes/$.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/$')({
component: NotFound,
})
function NotFound() {
const { _splat } = Route.useParams()
return <div>404 — {_splat}</div>
}
The _splat parameter contains the unmatched path. The $ without a name is the splat. The createFileRoute('/$') declares the splat route.
c. The layout routes and the flat naming convention
A layout route is a file that renders an <Outlet />. The users.tsx renders the layout for the /users path, and the child routes render inside it.
// src/routes/users.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/users')({
component: UsersLayout,
})
function UsersLayout() {
return (
<div>
<h1>Users</h1>
<Outlet />
</div>
)
}
The users.tsx file and the users/ directory can coexist. The users.tsx is the layout, and the users/index.tsx and users/$id.tsx are the children. The flat naming convention (users.profile.tsx, users.$id.tsx) is the alternative that avoids the directory nesting.
The users.profile.tsx becomes /users/profile. The users.$id.tsx becomes /users/:id. The users.$id.edit.tsx becomes /users/:id/edit. The dot is the separator between the segments. The flat convention is the preferred format for the file-based routing in TanStack Router .
The plugin generates the routeTree.gen.ts file from the file structure. The generated file imports the route components and builds the tree. The file is not meant to be edited.
// src/routeTree.gen.ts (generated)
import { Route as rootRoute } from './routes/__root'
import { Route as aboutRoute } from './routes/about'
import { Route as usersRoute } from './routes/users'
import { Route as usersIdRoute } from './routes/users.$id'
export const routeTree = rootRoute.addChildren([
aboutRoute,
usersRoute.addChildren([usersIdRoute]),
])
The addChildren method builds the tree. The root route is the base, and the child routes are added recursively. The createRouter({ routeTree }) uses this tree to match the URLs.
Complete Example Session
# ============================================
# PART 1: CREATE THE ROUTE DIRECTORY
# ============================================
mkdir -p src/routes
# ============================================
# PART 2: CREATE THE ROOT ROUTE
# ============================================
// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({ component: () => <Outlet /> })
# ============================================
# PART 3: CREATE THE INDEX ROUTE
# ============================================
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({ component: () => <h1>Home</h1> })
# ============================================
# PART 4: CREATE A STATIC ROUTE
# ============================================
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({ component: () => <h1>About</h1> })
# ============================================
# PART 5: CREATE A DYNAMIC ROUTE
# ============================================
// src/routes/users.$id.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/users/$id')({
component: () => {
const { id } = Route.useParams()
return <h1>User {id}</h1>
},
})
# ============================================
# PART 6: CREATE A LAYOUT ROUTE
# ============================================
// src/routes/users.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/users')({
component: () => <div><h1>Users</h1><Outlet /></div>,
})
# ============================================
# PART 7: CREATE A SPLAT ROUTE
# ============================================
// src/routes/$.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/$')({
component: () => {
const { _splat } = Route.useParams()
return <h1>404: {_splat}</h1>
},
})
# ============================================
# PART 8: START THE DEV SERVER
# ============================================
npm run dev
# The plugin generates routeTree.gen.ts
# ============================================
# PART 9: VERIFY THE ROUTES
# ============================================
# / → Home
# /about → About
# /users → Users layout
# /users/123 → User 123
# /unknown/path → 404: unknown/path
The nine parts trace the file-based routing: directory, root route, index route, static route, dynamic route, layout route, splat route, dev server, and verification.
Quick Reference
File Naming Conventions
| File | URL |
|---|---|
index.tsx | / |
about.tsx | /about |
users.$id.tsx | /users/:id |
users.index.tsx | /users/ |
users.tsx | /users (layout) |
$.tsx | /* (splat) |
__root.tsx | Root layout |
Generated File
| File | Purpose |
|---|---|
src/routeTree.gen.ts | Route tree |
| Ignored by | Linter, formatter |
createFileRoute Path
| File | Path |
|---|---|
about.tsx | '/about' |
users.$id.tsx | '/users/$id' |
users.index.tsx | '/users/' |
users.tsx | '/users' |
$.tsx | '/$' |
Hooks
| Hook | Returns |
|---|---|
Route.useParams() | Route params |
Route.useSearch() | Search params |
Route.useLoaderData() | Loader data |
Best Practices
✅ Do This:
export const Route = createFileRoute('/about')({ component: About })
<!-- Match the path to the file location --> # ✅
const { id } = Route.useParams()
<!-- Use typed params from the route --> # ✅
<Outlet />
<!-- Use Outlet in layout routes --> # ✅
❌ Don’t Do This:
# Mismatch the path and the file location
<!-- The plugin fails to generate the tree --> # ❌
# Edit routeTree.gen.ts manually
<!-- The plugin overwrites it --> # ❌
# Use both users.tsx and users/ directory
<!-- Conflicts with the flat convention --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Route not generated | Path mismatch | Check the file location |
| Dynamic route not matching | Missing $ | Use $id in filename |
| Index route not rendering | No index.tsx | Create the file |
| Layout not wrapping children | Missing <Outlet /> | Add Outlet |
| Splat not matching | Wrong $ location | Use $.tsx |
Real-World Examples
1. Index Route
// src/routes/index.tsx
export const Route = createFileRoute('/')({ component: Home })
2. Static Route
// src/routes/about.tsx
export const Route = createFileRoute('/about')({ component: About })
3. Dynamic Route
// src/routes/users.$id.tsx
export const Route = createFileRoute('/users/$id')({ component: UserProfile })
4. Index Child
// src/routes/users.index.tsx
export const Route = createFileRoute('/users/')({ component: UsersIndex })
5. Layout Route
// src/routes/users.tsx
export const Route = createFileRoute('/users')({ component: UsersLayout })
6. Splat Route
// src/routes/$.tsx
export const Route = createFileRoute('/$')({ component: NotFound })
7. Nested Dynamic
// src/routes/users.$id.edit.tsx
export const Route = createFileRoute('/users/$id/edit')({ component: UserEdit })
8. Flat Naming
src/routes/users.profile.tsx → /users/profile
9. Params Access
const { id } = Route.useParams()
10. Splat Access
const { _splat } = Route.useParams()
Visual
File to URL Mapping
┌──────────────────────────────────────────────┐
│ src/routes/ │
│ ├── __root.tsx → root layout │
│ ├── index.tsx → / │
│ ├── about.tsx → /about │
│ ├── users.tsx → /users (layout) │
│ ├── users.index.tsx → /users/ │
│ ├── users.$id.tsx → /users/:id │
│ └── $.tsx → /* (splat) │
└──────────────────────────────────────────────┘
Dynamic Segment
┌──────────────────────────────────────────────┐
│ users.$id.tsx │
│ → /users/:id │
│ → useParams() returns { id: "123" } │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ users.$id.edit.tsx │
│ → /users/:id/edit │
│ → useParams() returns { id: "123" } │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Root layout | __root.tsx |
| Index route | index.tsx |
| Static route | about.tsx |
| Dynamic route | users.$id.tsx |
| Layout route | users.tsx |
| Index child | users.index.tsx |
| Splat route | $.tsx |
| Generated tree | src/routeTree.gen.ts |
| Path declaration | createFileRoute('/path') |
| Params access | Route.useParams() |
Key takeaways:
- The filename is the URL. The
about.tsxbecomes/about. Theusers.$id.tsxbecomes/users/:id. The$prefix marks the dynamic segment. The dot separates the segments in the flat naming convention . - The
index.tsxis the default child. Thesrc/routes/index.tsxbecomes/. Thesrc/routes/users/index.tsxbecomes/users/. The index file is the landing page for the parent’s path. - The
users.tsxis the layout. It renders an<Outlet />for the child routes. The child routes render inside the layout. The layout file and the directory can coexist in older conventions; the flat naming (users.profile.tsx) avoids the ambiguity. - The
$.tsxis the splat. It matches any URL that the other routes do not match. The_splatparameter contains the unmatched path. The splat is the 404 page. - The
routeTree.gen.tsis generated. The plugin reads the route directory and generates the tree. The file is not meant to be edited. ThecreateRouter({ routeTree })uses the tree to match the URLs.
Remember: The file-based routing is the convention. The filename is the URL. The $ is the dynamic. The index is the default. The __root is the layout. The $ (bare) is the splat. The generated tree is the connection. The exam tests the file naming and the dynamic segment syntax.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!