TanStack Router: Type-Safe Client-Side Routing for React in 2026
Complete guide to TanStack Router: file-based routing, 100% type-safe search params, route loaders, nested layouts, and when to choose TanStack Router over React Router v7.
TanStack Router: Type-Safe Client-Side Routing for React in 2026
TanStack Router is the most type-safe routing library available for React. While React Router v7 merged with Remix to become a full-stack framework, TanStack Router went the opposite direction β it doubled down on client-side routing with end-to-end TypeScript inference that no other router matches.
If youβre building a data-heavy SPA where type safety across routes, search params, and loaders matters more than SSR, TanStack Router deserves serious consideration.
What Makes TanStack Router Different
The key design decision: everything is inferred from your route definitions. No useParams<{ id: string }>() generics. No casting searchParams.get('page') to a number. The router knows your route tree statically and TypeScript catches mismatches at compile time.
// Wrong param name β TypeScript error
const { postId } = Route.useParams() // β
post.id exists
const { postID } = Route.useParams() // β TypeScript error
This extends to:
- Route params
- Search params (with validation and serialization)
- Loader data
- Route context
- Navigation (
navigate,Link)
Installation
npm install @tanstack/react-router
# For file-based routing (recommended)
npm install -D @tanstack/router-plugin @tanstack/router-devtools
Add the Vite plugin:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
TanStackRouterVite(), // generates routeTree.gen.ts
react(),
],
})
File-Based Routing
TanStack Router uses a file convention similar to Next.js App Router, but generates a type-safe route tree at build time.
File Structure
src/routes/
βββ __root.tsx # Root layout (always rendered)
βββ index.tsx # / route
βββ about.tsx # /about
βββ posts/
β βββ index.tsx # /posts
β βββ $postId.tsx # /posts/:postId (dynamic segment)
β βββ $postId/
β βββ edit.tsx # /posts/:postId/edit
βββ dashboard/
βββ _layout.tsx # layout without URL segment
βββ index.tsx # /dashboard
βββ settings.tsx # /dashboard/settings
Root Route
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
export const Route = createRootRoute({
component: () => (
<>
<nav>
<Link to="/">Home</Link>
<Link to="/posts">Posts</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
<Outlet />
<TanStackRouterDevtools />
</>
),
})
Index Route
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome</h1>
}
Dynamic Routes with Loaders
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { fetchPost } from '../api'
export const Route = createFileRoute('/posts/$postId')({
loader: ({ params }) => fetchPost(params.postId), // params.postId is typed
component: PostPage,
})
function PostPage() {
const post = Route.useLoaderData() // fully typed from loader return
const { postId } = Route.useParams() // string, inferred from file name
return (
<article>
<h1>{post.title}</h1>
<p>ID: {postId}</p>
</article>
)
}
Type-Safe Search Params
This is where TanStack Router truly shines. Search params are first-class citizens with validation and type inference.
// src/routes/posts/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const postSearchSchema = z.object({
page: z.number().int().min(1).catch(1),
filter: z.enum(['all', 'published', 'draft']).catch('all'),
search: z.string().optional(),
})
export const Route = createFileRoute('/posts/')({
validateSearch: postSearchSchema,
component: PostsPage,
})
function PostsPage() {
const { page, filter, search } = Route.useSearch()
// page: number, filter: 'all' | 'published' | 'draft', search: string | undefined
// All inferred β no casting needed
const navigate = Route.useNavigate()
return (
<div>
<button onClick={() => navigate({ search: { page: page + 1, filter } })}>
Next Page
</button>
</div>
)
}
Invalid URL search params are automatically coerced to defaults via .catch() β no manual fallback logic needed.
Navigation
The Link Component
import { Link } from '@tanstack/react-router'
// TypeScript validates the `to` path and required params
<Link to="/posts/$postId" params={{ postId: '123' }}>
View Post
</Link>
// With search params
<Link
to="/posts/"
search={{ page: 2, filter: 'published' }}
>
Published Posts
</Link>
// Active link styling
<Link
to="/dashboard"
activeProps={{ className: 'font-bold text-blue-600' }}
inactiveProps={{ className: 'text-gray-600' }}
>
Dashboard
</Link>
Programmatic Navigation
import { useNavigate } from '@tanstack/react-router'
function SearchBar() {
const navigate = useNavigate({ from: '/posts/' })
const handleSearch = (query: string) => {
navigate({
search: (prev) => ({ ...prev, search: query, page: 1 }),
})
}
return <input onChange={(e) => handleSearch(e.target.value)} />
}
Route Loaders and Data Loading
TanStack Routerβs loader system is synchronous-first (runs before the component renders) with support for async data.
Parallel Loading
// src/routes/dashboard/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { fetchStats, fetchRecentActivity } from '../api'
export const Route = createFileRoute('/dashboard/')({
loader: async () => {
// Both requests fire in parallel
const [stats, activity] = await Promise.all([
fetchStats(),
fetchRecentActivity(),
])
return { stats, activity }
},
component: DashboardPage,
})
function DashboardPage() {
const { stats, activity } = Route.useLoaderData()
// stats and activity are fully typed
return (
<div>
<StatsPanel stats={stats} />
<ActivityFeed items={activity} />
</div>
)
}
Loader with Context
// src/router.tsx β inject dependencies into route context
const router = createRouter({
routeTree,
context: {
queryClient, // TanStack Query client
auth: undefined as AuthContext | undefined,
},
})
// src/routes/dashboard/settings.tsx
export const Route = createFileRoute('/dashboard/settings')({
beforeLoad: ({ context }) => {
if (!context.auth?.isLoggedIn) {
throw redirect({ to: '/login' })
}
},
loader: ({ context }) =>
context.queryClient.ensureQueryData(settingsQuery),
})
Nested Layouts
TanStack Routerβs layout system uses _layout files (pathless routes) for shared UI that doesnβt affect the URL:
// src/routes/dashboard/_layout.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard/_layout')({
component: DashboardLayout,
})
function DashboardLayout() {
return (
<div className="flex">
<aside className="w-64">
<DashboardNav />
</aside>
<main className="flex-1">
<Outlet />
</main>
</div>
)
}
All routes under /dashboard/ render inside DashboardLayout without adding _layout to the URL.
TanStack Router vs React Router v7
| Feature | TanStack Router | React Router v7 |
|---|---|---|
| TypeScript inference | End-to-end, no casting | Partial, generics required |
| Search param validation | Built-in (Zod/Valibot) | Manual |
| Primary use case | SPA, data-heavy apps | Full-stack apps |
| SSR support | Experimental | First-class |
| File-based routing | Yes (Vite plugin) | Yes (framework mode) |
| Nested layouts | Yes | Yes |
| Route loaders | Yes | Yes |
| Ecosystem maturity | Growing fast | Very mature |
| Bundle size | ~12 KB | ~15 KB (router only) |
Choose TanStack Router when:
- Building a SPA with complex navigation state
- Search params drive significant UI state
- You want TypeScript to catch routing mistakes at compile time
- You donβt need SSR
Choose React Router v7 when:
- You want a full-stack framework (formerly Remix)
- SSR or edge rendering is required
- You prefer the established ecosystem and conventions
Setting Up the Router
After file-based routing generates routeTree.gen.ts:
// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export const router = createRouter({
routeTree,
defaultPreload: 'intent', // preload on hover
defaultStaleTime: 5000,
})
// Register types globally
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
// src/main.tsx
import { RouterProvider } from '@tanstack/react-router'
import { router } from './router'
ReactDOM.createRoot(document.getElementById('root')!).render(
<RouterProvider router={router} />
)
DevTools
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
// Add to your root route component
<TanStackRouterDevtools position="bottom-right" />
The devtools panel shows:
- Current route tree and matched routes
- Loader data for each route
- Search param state
- Navigation history
Summary
TanStack Router offers the best TypeScript experience for client-side routing in 2026. The end-to-end type inference β from route definition to useParams(), useSearch(), and useLoaderData() β eliminates an entire class of runtime routing bugs.
If youβre building a Vite + React SPA where navigation complexity and type safety matter, TanStack Router is the stronger choice over React Router v7. For full-stack apps that need SSR, React Router v7 (Remix) remains the standard.
Start with the official docs and the Vite quickstart template β youβll have type-safe routes running in under 15 minutes.
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links β we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.