| name | tanstack-router-guide |
| description | Guide for using TanStack Router in React applications. Use when implementing routing, creating routes, handling navigation, working with route parameters, search params, loaders, or protected routes. Apply when the user asks about TanStack Router, file-based routing, type-safe navigation, or route authentication. |
| keywords | tanstack-router, routing, routes, navigation, loaders, search-params, file-based-routing, protected-routes, createFileRoute |
TanStack Router Guide
Overview
TanStack Router is a modern, type-safe routing library for React with file-based routing, built-in data loading, and powerful URL state management.
File-Based Routing
Route Structure
src/routes/
├── __root.tsx # Root layout
├── index.tsx # / (home)
├── _authenticated.tsx # Protected layout
├── _authenticated/
│ ├── dashboard.tsx # /dashboard
│ ├── users/
│ │ ├── index.tsx # /users
│ │ ├── $userId.tsx # /users/:userId
│ │ └── $userId.edit.tsx # /users/:userId/edit
└── login.tsx # /login
Root Route
import { createRootRoute, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: () => (
<div>
<nav>Navigation Bar</nav>
<Outlet />
</div>
),
})
Basic Route
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: () => <h1>Home Page</h1>,
})
Dynamic Route with Params
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: () => {
const { postId } = Route.useParams()
return <div>Post {postId}</div>
},
})
Route Parameters
Path Parameters
Use $ prefix for dynamic segments:
export const Route = createFileRoute('/users/$userId')({
component: () => {
const { userId } = Route.useParams()
return <div>User ID: {userId}</div>
},
})
Search Parameters
Use validateSearch with Zod schema:
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().int().positive().default(1),
filter: z.string().optional(),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})
export const Route = createFileRoute('/products')({
validateSearch: productSearchSchema,
component: () => {
const { page, filter, sort } = Route.useSearch()
return (
<div>
<h1>Products (Page {page})</h1>
{filter && <p>Filter: {filter}</p>}
<p>Sort: {sort}</p>
</div>
)
},
})
Data Loading
Loader with Dependencies
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
async function fetchPosts(page: number, filter?: string) {
const params = new URLSearchParams({
page: String(page),
...(filter && { filter })
})
const response = await fetch(`/api/posts?${params}`)
return response.json()
}
export const Route = createFileRoute('/posts')({
validateSearch: z.object({
page: z.number().default(1),
filter: z.string().optional(),
}),
loaderDeps: ({ search }) => ({
page: search.page,
filter: search.filter,
}),
loader: async ({ deps }) => {
return await fetchPosts(deps.page, deps.filter)
},
staleTime: 5000,
gcTime: 30000,
component: () => {
const posts = Route.useLoaderData()
const { page } = Route.useSearch()
return (
<div>
<h1>Posts (Page {page})</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
},
})
Loader with Single Item
async function fetchPost(id: string) {
const response = await fetch(`/api/posts/${id}`)
return response.json()
}
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return await fetchPost(params.postId)
},
component: () => {
const post = Route.useLoaderData()
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
},
})
Protected Routes
Authentication Layout
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: {
redirect: location.href,
},
})
}
},
component: () => <Outlet />,
})
Protected Route with User Context
import { createFileRoute, redirect } from '@tanstack/react-router'
import { getCurrentUser } from '@/lib/auth'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: async ({ location }) => {
const user = await getCurrentUser()
if (!user) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
return { user }
},
})
Using User Context in Protected Route
export const Route = createFileRoute('/_authenticated/dashboard')({
component: () => {
const { user } = Route.useRouteContext()
return (
<div>
<h1>Welcome, {user.email}!</h1>
</div>
)
},
})
Navigation
Link Component
import { Link } from '@tanstack/react-router'
function Navigation() {
return (
<nav>
{/* Basic link */}
<Link to="/about">About</Link>
{/* With params */}
<Link
to="/posts/$postId"
params={{ postId: '123' }}
>
View Post
</Link>
{/* With search params */}
<Link
to="/products"
search={{ page: 1, filter: 'active' }}
>
Products
</Link>
{/* Active styling */}
<Link
to="/dashboard"
activeProps={{ className: 'font-bold text-blue-600' }}
activeOptions={{ exact: true }}
>
Dashboard
</Link>
{/* Preload on hover */}
<Link
to="/heavy-page"
preload="intent"
preloadDelay={100}
>
Heavy Page
</Link>
</nav>
)
}
Programmatic Navigation
import { useNavigate } from '@tanstack/react-router'
function Component() {
const navigate = useNavigate()
const handleClick = () => {
navigate({
to: '/posts/$postId',
params: { postId: '123' }
})
}
const handleSearch = () => {
navigate({
to: '.',
search: (prev) => ({ ...prev, page: prev.page + 1 })
})
}
const handleReplace = () => {
navigate({
to: '/login',
replace: true
})
}
return (
<div>
<button onClick={handleClick}>View Post</button>
<button onClick={handleSearch}>Next Page</button>
</div>
)
}
Route Metadata
staticData for Route Meta
export const Route = createFileRoute('/_authenticated/users/')({
component: UserList,
staticData: {
meta: {
title: 'Users',
titleKey: 'users.title',
},
},
})
Common Patterns
List with Detail Pages
export const Route = createFileRoute('/_authenticated/users/')({
loader: async () => {
return await fetchUsers()
},
component: () => {
const users = Route.useLoaderData()
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>
<Link
to="/users/$userId"
params={{ userId: user.id }}
>
{user.name}
</Link>
</li>
))}
</ul>
</div>
)
},
})
export const Route = createFileRoute('/_authenticated/users/$userId')({
loader: async ({ params }) => {
return await fetchUser(params.userId)
},
component: () => {
const user = Route.useLoaderData()
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
},
})
CRUD Routes
For detailed examples, see references/route-patterns.md.
Best Practices
- Use validateSearch for type-safe search params
- Use loaderDeps for intelligent caching
- Use beforeLoad for authentication/authorization
- Pass user context from layout to child routes
- Use Link component for type-safe navigation
- Use relative navigation with
. and ..
- Preload routes on user intent
- Cache loader data with staleTime/gcTime
Additional Resources