tanstack-start-routes
Installation
SKILL.md
TanStack Start Routes
TanStack Start uses file-based routing with TanStack Router. Routes are defined as files in the src/routes directory.
When to Use
- Adding new pages to your application
- Setting up nested layouts
- Configuring route parameters
- Creating pathless layout routes
- Organizing route structure
Directory Structure
src/
├── routes/
│ ├── __root.tsx # Root layout (required)
│ ├── index.tsx # Home page (/)
│ ├── about.tsx # About page (/about)
│ ├── posts.tsx # Posts layout (/posts)
│ ├── posts.index.tsx # Posts index (/posts)
│ ├── posts.$postId.tsx # Dynamic post (/posts/:postId)
│ ├── _auth.tsx # Pathless layout (no URL segment)
│ ├── _auth.login.tsx # Login under auth layout (/login)
│ └── _auth.register.tsx # Register under auth layout (/register)
├── router.tsx # Router configuration
└── routeTree.gen.ts # Auto-generated route tree
Root Route
The root route wraps all other routes and defines the document shell:
// src/routes/__root.tsx
import {
Outlet,
createRootRoute,
HeadContent,
Scripts
} from '@tanstack/react-router';
import type { ReactNode } from 'react';
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'My App' },
],
}),
component: RootComponent,
});
function RootComponent() {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<Header />
<main>
<Outlet />
</main>
<Scripts />
</body>
</html>
);
}
function Header() {
return (
<header>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/posts">Posts</Link>
</nav>
</header>
);
}
Basic Route
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/about')({
component: AboutComponent,
});
function AboutComponent() {
return (
<div>
<h1>About Us</h1>
<p>This is the about page.</p>
</div>
);
}
Index Route
Index routes render when the parent path is matched exactly:
// src/routes/index.tsx (renders at /)
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: HomeComponent,
});
function HomeComponent() {
return <h1>Welcome Home</h1>;
}
// src/routes/posts.index.tsx (renders at /posts)
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/posts/')({
component: PostsIndexComponent,
});
function PostsIndexComponent() {
return <h2>Select a post from the list</h2>;
}
Layout Routes
Layout routes wrap child routes with shared UI:
// src/routes/posts.tsx
import { createFileRoute, Outlet, Link } from '@tanstack/react-router';
export const Route = createFileRoute('/posts')({
component: PostsLayoutComponent,
});
function PostsLayoutComponent() {
return (
<div className="posts-layout">
<aside>
<h2>Posts</h2>
<nav>
<Link to="/posts/1">Post 1</Link>
<Link to="/posts/2">Post 2</Link>
</nav>
</aside>
<div className="content">
<Outlet /> {/* Child routes render here */}
</div>
</div>
);
}
Dynamic Route Parameters
Use $paramName for dynamic segments:
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
});
function PostComponent() {
const { postId } = Route.useParams();
return (
<article>
<h1>Post {postId}</h1>
</article>
);
}
Multiple Parameters
// src/routes/users.$userId.posts.$postId.tsx
// Matches: /users/123/posts/456
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/users/$userId/posts/$postId')({
component: UserPostComponent,
});
function UserPostComponent() {
const { userId, postId } = Route.useParams();
return <div>User {userId}'s Post {postId}</div>;
}
Pathless Layout Routes
Prefix with _ for layouts that don't add URL segments:
// src/routes/_auth.tsx
// This creates a layout but adds no URL segment
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router';
export const Route = createFileRoute('/_auth')({
beforeLoad: async ({ context }) => {
// Redirect if already authenticated
if (context.user) {
throw redirect({ to: '/dashboard' });
}
},
component: AuthLayout,
});
function AuthLayout() {
return (
<div className="auth-container">
<div className="auth-card">
<Outlet />
</div>
</div>
);
}
// src/routes/_auth.login.tsx
// Renders at /login (not /_auth/login)
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_auth/login')({
component: LoginComponent,
});
function LoginComponent() {
return <form>Login Form</form>;
}
Catch-All (Splat) Routes
Use $ for catch-all routes:
// src/routes/files.$.tsx
// Matches: /files/any/path/here
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/files/$')({
component: FilesComponent,
});
function FilesComponent() {
const { _splat } = Route.useParams();
// _splat = "any/path/here"
return <div>File path: {_splat}</div>;
}
404 Not Found Route
// src/routes/__root.tsx
import { createRootRoute } from '@tanstack/react-router';
export const Route = createRootRoute({
component: RootComponent,
notFoundComponent: NotFoundComponent,
});
function NotFoundComponent() {
return (
<div>
<h1>404 - Page Not Found</h1>
<Link to="/">Go Home</Link>
</div>
);
}
Route Configuration Options
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/posts/$postId')({
// Component to render
component: PostComponent,
// Error boundary component
errorComponent: ErrorComponent,
// Loading/pending component
pendingComponent: LoadingComponent,
// Not found component (for this route)
notFoundComponent: NotFoundComponent,
// Validate and parse params
parseParams: (params) => ({
postId: parseInt(params.postId, 10),
}),
// Serialize params back to strings
stringifyParams: (params) => ({
postId: String(params.postId),
}),
// Validate search params
validateSearch: (search) => ({
page: Number(search.page) || 1,
}),
});
File Naming Conventions
| File Name | Route Path |
|---|---|
index.tsx |
/ |
about.tsx |
/about |
posts.tsx |
/posts (layout) |
posts.index.tsx |
/posts (index) |
posts.$postId.tsx |
/posts/:postId |
posts.$postId.edit.tsx |
/posts/:postId/edit |
_layout.tsx |
Pathless layout |
_layout.page.tsx |
/page with layout |
files.$.tsx |
/files/* (catch-all) |
Directory vs Flat Routes
Both styles work and can be mixed:
Flat (dot notation):
routes/
├── posts.tsx
├── posts.index.tsx
└── posts.$postId.tsx
Directory style:
routes/
└── posts/
├── route.tsx # Layout
├── index.tsx # Index
└── $postId.tsx # Dynamic
Router Configuration
// src/router.tsx
import { createRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';
export function getRouter() {
const router = createRouter({
routeTree,
scrollRestoration: true,
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
});
return router;
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>;
}
}
Common Patterns
Protected Routes
// src/routes/_protected.tsx
import { createFileRoute, redirect } from '@tanstack/react-router';
export const Route = createFileRoute('/_protected')({
beforeLoad: async ({ context }) => {
if (!context.user) {
throw redirect({
to: '/login',
search: { redirect: location.href },
});
}
},
component: ProtectedLayout,
});
Route Groups (Organization Only)
Use parentheses for grouping without affecting URLs:
routes/
├── (marketing)/
│ ├── about.tsx # /about
│ └── pricing.tsx # /pricing
└── (app)/
├── dashboard.tsx # /dashboard
└── settings.tsx # /settings