TanStack Router v1 is now stable, and it brings a fundamentally different approach to routing in React applications. Unlike traditional routers, TanStack Router treats routes as first-class TypeScript citizens — giving you end-to-end type safety from route definition all the way to link generation.
Why TanStack Router?
Before diving into setup, it's worth understanding why TanStack Router exists. The core problems it solves:
- Type-safe links —
<Link to="/blog/$slug" params={{ slug: 'hello' }}>is fully typed - Built-in data loading — route loaders with suspense and streaming support
- Search params as state — validated, typed URL search parameters
- Nested layouts — compose complex UIs with nested route trees
Installation
pnpm add @tanstack/react-router
pnpm add -D @tanstack/router-plugin @tanstack/router-devtools
File-Based Routing Setup
Add the router plugin to your Vite config:
// vite.config.ts
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({ autoCodeSplitting: true }),
react(),
],
})
Creating Your First Route
Create src/routes/index.tsx:
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome to my app</h1>
}
Type-Safe Navigation
The real power shows when you navigate between routes:
import { Link, useNavigate } from '@tanstack/react-router'
// Fully typed — TypeScript will catch invalid routes
function NavBar() {
const navigate = useNavigate()
return (
<nav>
<Link to="/" className="...">Home</Link>
<Link to="/blog/$slug" params={{ slug: 'my-post' }}>Blog</Link>
<button onClick={() => navigate({ to: '/dashboard' })}>
Dashboard
</button>
</nav>
)
}
Route Loaders
TanStack Router has first-class support for data loading at the route level:
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/users/$userId')({
loader: async ({ params }) => {
const user = await fetchUser(params.userId)
return { user }
},
component: UserPage,
})
function UserPage() {
const { user } = Route.useLoaderData()
return <div>{user.name}</div>
}
Search Parameters
One of TanStack Router's most powerful features is type-safe search params:
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const searchSchema = z.object({
page: z.number().default(1),
filter: z.string().optional(),
})
export const Route = createFileRoute('/products')({
validateSearch: searchSchema,
component: ProductsPage,
})
function ProductsPage() {
const { page, filter } = Route.useSearch()
// page is typed as number, filter as string | undefined
return <ProductGrid page={page} filter={filter} />
}
Nested Layouts
Build complex UI compositions using layout routes:
src/routes/
__root.tsx ← Root layout (header, footer)
dashboard.tsx ← Dashboard layout (sidebar)
dashboard/
index.tsx ← /dashboard
analytics.tsx ← /dashboard/analytics
settings.tsx ← /dashboard/settings
Each layout file renders <Outlet /> to display its children:
// dashboard.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
component: DashboardLayout,
})
function DashboardLayout() {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">
<Outlet />
</main>
</div>
)
}
What's Next?
Now that you have the basics working, explore:
- Context — share data between parent and child routes
- Pending components — show loading states during navigation
- Error boundaries — handle route errors gracefully
- Route guards — redirect unauthenticated users
TanStack Router's combination of type safety, file-based conventions, and powerful data loading makes it an excellent foundation for any React application.