tanstack-router-guide
Type-safe, file-based router for React with first-class search params, data loading, and code splitting. Use when user asks to "create routes with TanStack Router", "set up file-based routing", "add search params", "use loaders", "protect routes with auth", "add code splitting", or asks about @tanstack/react-router, createFileRoute, createRouter, routeTree.gen.ts, useSearch, useParams, useNavigate, useBlocker, useMatch, useRouterState, beforeLoad, or route configuration. Do NOT use for TanStack Start server functions, Next.js App Router, React Router (without migration context), or Remix routing. Covers routing setup, navigation, search/path params, data loading, authentication, code splitting, SSR, error handling, testing, deployment, and bundler configuration (Vite, Webpack, Rspack, esbuild).
NPX Install
npx skill4agent add vcode-sh/vibe-tools tanstack-router-guideTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →TanStack Router Guide (React)
Install
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin
# Optional: devtools
npm install @tanstack/react-router-devtoolsQuick Start with Vite (Recommended)
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
react(), // Must come AFTER tanstackRouter
],
})// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: () => (
<>
<nav>
<Link to="/" activeProps={{ className: 'font-bold' }}>Home</Link>
<Link to="/about" activeProps={{ className: 'font-bold' }}>About</Link>
</nav>
<Outlet />
<TanStackRouterDevtools />
</>
),
})// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: () => <div>Welcome Home!</div>,
})
// src/routes/about.tsx
export const Route = createFileRoute('/about')({
component: () => <div>About Page</div>,
})// src/main.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// Register router type globally for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)File-Based Routing (Naming Conventions)
src/routes/| File Name | URL Path | Purpose |
|---|---|---|
| N/A | Root layout (always rendered) |
| | Index route |
| | Static route |
| | Layout route (renders Outlet) |
| | Index for /posts |
| | Dynamic segment |
| N/A | Pathless layout (wraps children, no URL) |
| | Child of pathless layout |
| | Non-nested (escapes parent layout) |
| | Splat/catch-all route |
| | Optional path parameter |
| N/A | Excluded from routing |
| | Route group (organizational only) |
routeTree.gen.tsNavigation
import { Link, useNavigate } from '@tanstack/react-router'
// Declarative - Link component
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
<Link to="/posts" search={{ page: 2, sort: 'asc' }}>Page 2</Link>
<Link to=".." from="/posts/$postId">Back to Posts</Link>
// Active styling
<Link to="/about" activeProps={{ className: 'active' }} inactiveProps={{ className: 'dim' }}>
About
</Link>
// Programmatic - useNavigate
const navigate = useNavigate()
navigate({ to: '/posts/$postId', params: { postId: '123' } })
navigate({ to: '/posts', search: (prev) => ({ ...prev, page: 2 }) })
navigate({ to: '..', from: '/posts/$postId' }) // RelativeSearch Params (Validated & Type-Safe)
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
export const Route = createFileRoute('/posts')({
validateSearch: z.object({
page: z.number().catch(1),
sort: z.enum(['asc', 'desc']).optional(),
filter: z.string().optional(),
}),
component: PostsPage,
})
function PostsPage() {
const { page, sort, filter } = Route.useSearch() // Fully typed
const navigate = Route.useNavigate()
return (
<button onClick={() => navigate({ search: (prev) => ({ ...prev, page: prev.page + 1 }) })}>
Next Page
</button>
)
}import { retainSearchParams, stripSearchParams } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
validateSearch: z.object({ page: z.number().catch(1), q: z.string().optional() }),
search: {
middlewares: [
retainSearchParams(['q']), // Keep 'q' across navigations
stripSearchParams({ page: 1 }), // Strip 'page' when it equals default
],
},
})Data Loading
// Basic loader with path params
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
component: PostPage,
})
function PostPage() {
const { post } = Route.useLoaderData() // Fully typed
return <div>{post.title}</div>
}// Loader with search-param dependencies
export const Route = createFileRoute('/posts')({
validateSearch: z.object({ page: z.number().catch(1) }),
loaderDeps: ({ search }) => ({ page: search.page }),
loader: async ({ deps }) => fetchPosts(deps.page),
component: PostsPage,
})staleTimeshouldReloadpendingMspendingMinMsgcTimeloaderDepsOptional Path Parameters
{-$paramName}// src/routes/posts.{-$category}.tsx -> /posts or /posts/tech
export const Route = createFileRoute('/posts/{-$category}')({
component: () => {
const { category } = Route.useParams() // category: string | undefined
return <div>{category ? `Posts in ${category}` : 'All Posts'}</div>
},
})
// Navigation: pass undefined to omit the segment
<Link to="/posts/{-$category}" params={{ category: undefined }}>All Posts</Link>
<Link to="/posts/{-$category}" params={{ category: 'tech' }}>Tech Posts</Link>Router Context (Dependency Injection)
import { createRootRouteWithContext } from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
interface RouterContext {
queryClient: QueryClient
auth: AuthState
}
// Root route
const rootRoute = createRootRouteWithContext<RouterContext>()({
component: RootComponent,
})
// Use in any route
export const Route = createFileRoute('/posts')({
beforeLoad: ({ context }) => {
// context.queryClient and context.auth available here
},
loader: ({ context }) => context.queryClient.ensureQueryData(postsQueryOptions()),
})
// Provide context when creating router
const router = createRouter({
routeTree,
context: { queryClient, auth: { user: null } },
})Authentication (Protected Routes)
// src/routes/_auth.tsx - Pathless layout for protected routes
export const Route = createFileRoute('/_auth')({
beforeLoad: async ({ context, location }) => {
if (!context.auth.user) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
},
component: () => <Outlet />,
})
// src/routes/_auth.dashboard.tsx - Protected route
export const Route = createFileRoute('/_auth/dashboard')({
component: () => <div>Protected Dashboard</div>,
})Error Handling
import { notFound } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
if (!post) throw notFound()
return { post }
},
notFoundComponent: () => <div>Post not found!</div>,
errorComponent: ({ error, reset }) => (
<div>
<p>Error: {error.message}</p>
<button onClick={reset}>Retry</button>
</div>
),
})
// Global defaults on router
const router = createRouter({
routeTree,
defaultNotFoundComponent: () => <div>Page not found</div>,
defaultErrorComponent: ({ error }) => <div>Error: {error.message}</div>,
})Essential Hooks
| Hook | Purpose |
|---|---|
| Current validated search params |
| Current path params |
| Data from route loader |
| Route's context |
| Navigate from current route |
| Navigate from any component |
| Access router instance |
| Reactive router state |
| Match data for specific route |
| Block navigation (dirty forms) |
references/api-hooks.mdKey Rules
- Plugin order: must come BEFORE
tanstackRouter()in Vite configreact() - Commit routeTree.gen.ts: It's runtime code, not a build artifact
- Module declaration: Always register router type for global type inference
- Export as Route: File-based routes must export
const Route = createFileRoute(...) - Pathless layouts: Prefix with (e.g.,
_) for layout-only routes_auth.tsx - Non-nested routes: Use suffix to escape parent layout (e.g.,
_)posts_.$id.edit.tsx - Ignore generated file: Add to
routeTree.gen.ts,.prettierignore.eslintignore - Route matching order: Index > Static > Dynamic > Splat (automatic)
- Don't export route properties: Exported components/loaders break code splitting
- Validation adapters: Valibot/ArkType work directly; Zod needs adapter
zodValidator - Outlet required: Every route with children must render ; routes without a
<Outlet />auto-rendercomponent<Outlet /> - Type safety tip: Use methods over standalone hooks for automatic type inference
Route.useX()
Reference Files
API
- — All 19 hooks with signatures, options, and examples
references/api-hooks.md - — Link, Outlet, Await, Block, HeadContent, CatchNotFound, and more
references/api-components.md - — createRouter, createFileRoute, redirect, notFound, linkOptions, search middleware
references/api-functions.md - — Router instance methods, events, and route type API
references/api-router-instance.md - — NavigateOptions, RouterState, RouteMatch, type utilities, deprecated items
references/api-types.md
Patterns
- — Path parameters, search params (Zod/Valibot/ArkType), loaderDeps, middlewares
references/patterns-params.md - — Link options, custom links, navigation blocking, history types
references/patterns-links-blocking.md - — Data loading, mutations, TanStack Query integration, not-found handling
references/patterns-data.md - — Authentication, RBAC, router context, preloading strategies
references/patterns-auth.md
Configuration
- — Vite, Webpack, Rspack, esbuild, Router CLI setup and plugin options
references/config-bundlers.md - — File naming conventions, route matching, code-based routing
references/config-routing.md - — Virtual file routes, physical routes, __virtual.ts subtrees
references/config-virtual-routes.md - — All createRouter() options: core, preloading, data loading, search, scroll, URL behavior
references/config-router-options.md - — All createFileRoute/createRoute options: components, search, loader, lifecycle, head, SSR
references/config-route-options.md - — DevTools modes, production devtools, IDE configuration
references/config-devtools.md
Advanced
- — SSR streaming/non-streaming, dehydration/hydration, deferred data
references/advanced-ssr.md - — Automatic/manual splitting, split groupings, lazy routes
references/advanced-code-splitting.md - — URL rewrites, route masking, custom search serialization
references/advanced-url-features.md - — Type safety, TS performance tips, render optimizations, view transitions
references/advanced-optimization.md - — Document head management, scroll restoration, i18n
references/advanced-head-scroll.md
Operations
- — FAQ, common errors, debugging guide, performance issues
references/troubleshooting.md - — Deployment (8 platforms), environment variables, framework integrations
references/deployment-integrations.md - — Testing setup, route testing patterns, migration guides
references/testing-migration.md