| name | TanStack Router |
| description | Build type-safe, file-based React routing with TanStack Router. Supports client-side navigation, route loaders, and TanStack Query integration. Use when implementing file-based routing patterns, building SPAs with TypeScript routing, or troubleshooting devtools dependency errors, type safety issues, or Vite bundling problems. |
TanStack Router
Type-safe, file-based routing for React SPAs with route-level data loading and TanStack Query integration
Quick Start
Last Updated: 2026-01-03 Version: @tanstack/react-router@1.144.0
npm install @tanstack/react-router @tanstack/router-devtools
npm install -D @tanstack/router-plugin
# Optional: Zod validation adapter
npm install @tanstack/zod-adapter zod
Vite Config (TanStackRouterVite MUST come before react()):
// vite.config.ts
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [TanStackRouterVite(), react()], // Order matters!
})
File Structure:
src/routes/
├── __root.tsx → createRootRoute() with <Outlet />
├── index.tsx → createFileRoute('/')
└── posts.$postId.tsx → createFileRoute('/posts/$postId')
App Setup:
import { createRouter, RouterProvider } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen' // Auto-generated by plugin
const router = createRouter({ routeTree })
<RouterProvider router={router} />
Core Patterns
Type-Safe Navigation (routes auto-complete, params typed):
<Link to="/posts/$postId" params={{ postId: '123' }} />
<Link to="/invalid" /> // ❌ TypeScript error
Route Loaders (data fetching before render):
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => ({ post: await fetchPost(params.postId) }),
component: ({ useLoaderData }) => {
const { post } = useLoaderData() // Fully typed!
return <h1>{post.title}</h1>
},
})
TanStack Query Integration (prefetch + cache):
const postOpts = (id: string) => queryOptions({
queryKey: ['posts', id],
queryFn: () => fetchPost(id),
})
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postOpts(params.postId)),
component: () => {
const { postId } = Route.useParams()
const { data } = useQuery(postOpts(postId))
return <h1>{data.title}</h1>
},
})
Virtual File Routes (v1.140+)
Programmatic route configuration when file-based conventions don't fit your needs:
Install: npm install @tanstack/virtual-file-routes
Vite Config:
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
target: 'react',
virtualRouteConfig: './routes.ts', // Point to your routes file
}),
react(),
],
})
routes.ts (define routes programmatically):
import { rootRoute, route, index, layout, physical } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
index('home.tsx'),
route('/posts', 'posts/posts.tsx', [
index('posts/posts-home.tsx'),
route('$postId', 'posts/posts-detail.tsx'),
]),
layout('first', 'layout/first-layout.tsx', [
route('/nested', 'nested.tsx'),
]),
physical('/classic', 'file-based-subtree'), // Mix with file-based
])
Use Cases: Custom route organization, mixing file-based and code-based, complex nested layouts.
Search Params Validation (Zod Adapter)
Type-safe URL search params with runtime validation:
Basic Pattern (inline validation):
import { z } from 'zod'
export const Route = createFileRoute('/products')({
validateSearch: (search) => z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
}).parse(search),
})
Recommended Pattern (Zod adapter with fallbacks):
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'
const searchSchema = z.object({
query: z.string().min(1).max(100),
page: fallback(z.number().int().positive(), 1),
sortBy: z.enum(['name', 'date', 'relevance']).optional(),
})
export const Route = createFileRoute('/search')({
validateSearch: zodValidator(searchSchema),
// Type-safe: Route.useSearch() returns typed params
})
Why .catch() over .default(): Use .catch() to silently fix malformed params. Use .default() + errorComponent to show validation errors.
Error Boundaries
Handle errors at route level with typed error components:
Route-Level Error Handling:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
if (!post) throw new Error('Post not found')
return { post }
},
errorComponent: ({ error, reset }) => (
<div>
<p>Error: {error.message}</p>
<button onClick={reset}>Retry</button>
</div>
),
})
Default Error Component (global fallback):
const router = createRouter({
routeTree,
defaultErrorComponent: ({ error }) => (
<div className="error-page">
<h1>Something went wrong</h1>
<p>{error.message}</p>
</div>
),
})
Not Found Handling:
export const Route = createFileRoute('/posts/$postId')({
notFoundComponent: () => <div>Post not found</div>,
})
Authentication with beforeLoad
Protect routes before they load (no flash of protected content):
Single Route Protection:
import { redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: { redirect: location.pathname }, // Save for post-login
})
}
},
})
Protect Multiple Routes (layout route pattern):
// routes/(authenticated)/route.tsx - protects all children
export const Route = createFileRoute('/(authenticated)')({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
},
})
Passing Auth Context (from React hooks):
// main.tsx - pass auth state to router
function App() {
const auth = useAuth() // Your auth hook
return (
<RouterProvider
router={router}
context={{ auth }} // Available in beforeLoad
/>
)
}
Known Issues & Solutions
Issue #1: Devtools Dependency Resolution
- Error: Build fails with
@tanstack/router-devtools-corenot found - Fix:
npm install @tanstack/router-devtools
Issue #2: Vite Plugin Order (CRITICAL)
- Error: Routes not auto-generated,
routeTree.gen.tsmissing - Fix: TanStackRouterVite MUST come before react() in plugins array
- Why: Plugin processes route files before React compilation
Issue #3: Type Registration Missing
- Error:
<Link to="...">not typed, no autocomplete - Fix: Import
routeTreefrom./routeTree.genin main.tsx to register types
Issue #4: Loader Not Running
- Error: Loader function not called on navigation
- Fix: Ensure route exports
Routeconstant:export const Route = createFileRoute('/path')({ loader: ... })
Issue #5: Memory Leak with TanStack Form
- Error: Production crashes when using TanStack Form + Router
- Source: GitHub Issue #5734 (known issue, still open as of v1.144)
- Workaround: Use React Hook Form or Formik instead
Issue #6: Virtual Routes Index/Layout Conflict
- Error: route.tsx and index.tsx conflict when using
physical()in virtual routing - Source: GitHub Issue #5421
- Fix: Use pathless route instead:
_layout.tsx+_layout.index.tsx
Issue #7: Search Params Type Inference
- Error: Type inference not working with
zodSearchValidator - Source: GitHub Issue #3100 (regression since v1.81.5)
- Fix: Use
zodValidatorfrom@tanstack/zod-adapterinstead
Issue #8: TanStack Start Validators on Reload
- Error:
validateSearchnot working on page reload in TanStack Start - Source: GitHub Issue #3711
- Note: Works on client-side navigation, fails on direct page load
Cloudflare Workers Integration
Vite Config (add @cloudflare/vite-plugin):
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [TanStackRouterVite(), react(), cloudflare()],
})
API Routes Pattern (fetch from Workers backend):
// Worker: functions/api/posts.ts
export async function onRequestGet({ env }) {
const { results } = await env.DB.prepare('SELECT * FROM posts').all()
return Response.json(results)
}
// Router: src/routes/posts.tsx
export const Route = createFileRoute('/posts')({
loader: async () => fetch('/api/posts').then(r => r.json()),
})
Related Skills: tanstack-query (data fetching), react-hook-form-zod (form validation), cloudflare-worker-base (API backend), tailwind-v4-shadcn (UI)
Related Packages: @tanstack/zod-adapter (search validation), @tanstack/virtual-file-routes (programmatic routes)