--- name: tanstack-router description: | TanStack Router best practices for type-safe routing, file-based routing, data loading, search params, and navigation in React. Use when building React SPAs with complex routing, implementing type-safe search params, setting up route loaders, integrating with TanStack Query, or configuring code splitting and preloading. metadata: tags: tanstack-router, routing, react, typescript, file-based-routing, search-params, data-loading, code-splitting --- # TanStack Router **Version**: @tanstack/react-router@1.x **Requires**: React 18.0+, TypeScript 5.0+, Vite (recommended) ## Quick Setup ```bash npm install @tanstack/react-router @tanstack/react-router-devtools npm install -D @tanstack/router-plugin ``` ### Vite Plugin ```ts // 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(), react()], }) ``` ### Root Route ```tsx // src/routes/__root.tsx import { createRootRoute, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' export const Route = createRootRoute({ component: () => ( <> ), }) ``` ### Unified Devtools (Recommended with Multiple TanStack Libraries) If using Router + Query (or other TanStack libraries), use the unified `TanStackDevtools` shell instead of individual devtools components: ```bash npm install -D @tanstack/react-devtools ``` ```tsx // src/routes/__root.tsx import { createRootRoute, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' import { TanStackDevtools } from '@tanstack/react-devtools' export const Route = createRootRoute({ component: () => ( <> }, // Add more plugins: Query, etc. ]} /> ), }) ``` Use `*Panel` variants (`TanStackRouterDevtoolsPanel`, `ReactQueryDevtoolsPanel`) when embedding inside `TanStackDevtools`. ### Router Creation & Type Registration ```tsx // src/router.tsx import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' export const router = createRouter({ routeTree }) // Register router type for global inference declare module '@tanstack/react-router' { interface Register { router: typeof router } } ``` ### Entry Point ```tsx // src/main.tsx import { RouterProvider } from '@tanstack/react-router' import { router } from './router' function App() { return } ``` ### File Structure ``` src/routes/ ├── __root.tsx # Root layout (always rendered) ├── index.tsx # "/" route ├── about.tsx # "/about" route ├── posts.tsx # "/posts" layout ├── posts.index.tsx # "/posts" index ├── posts.$postId.tsx # "/posts/:postId" dynamic route ├── _auth.tsx # Pathless layout (auth guard) ├── _auth.dashboard.tsx # "/dashboard" (wrapped by _auth) └── (settings)/ ├── settings.tsx # Route group └── settings.profile.tsx ``` ## Rule Categories | Priority | Category | Rule File | Impact | |----------|----------|-----------|--------| | CRITICAL | Type Safety | `rules/ts-type-safety.md` | Prevents runtime errors, enables refactoring | | CRITICAL | File-Based Routing | `rules/org-file-based-routing.md` | Ensures maintainable route structure | | HIGH | Router Config | `rules/router-configuration.md` | Global defaults for preload, scroll, errors | | HIGH | Data Loading | `rules/load-data-loading.md` | Optimizes data fetching, prevents waterfalls | | HIGH | Query Integration | `rules/load-query-integration.md` | TanStack Query + Router wiring | | HIGH | Search Params | `rules/search-params.md` | Type-safe URL state management | | HIGH | Error Handling | `rules/err-error-handling.md` | Graceful error and 404 handling | | MEDIUM | Navigation | `rules/nav-navigation.md` | Type-safe links and programmatic nav | | MEDIUM | Code Splitting | `rules/split-code-splitting.md` | Reduces bundle size | | MEDIUM | Preloading | `rules/pre-preloading.md` | Improves perceived performance | | LOW | Route Context | `rules/ctx-route-context.md` | Dependency injection and auth guards | ## Critical Rules ### Always Do - **Register router type** — declare module `@tanstack/react-router` with `Register.router` for global type inference - **Use `from` parameter** in hooks (`useSearch`, `useParams`, `useLoaderData`) to get exact types for the current route - **Validate search params** — use `validateSearch` with any Standard Schema library (Zod, Valibot, Yup, ArkType, etc.) - **Use file-based routing** — let the plugin generate the route tree, don't maintain it manually - **Use loaders for data** — fetch in `loader`, not in components (prevents waterfalls, enables preloading) ### Never Do - **Don't skip type registration** — without it, all hooks return `unknown` unions - **Don't fetch data in useEffect** — use `loader` or `beforeLoad` instead - **Don't use string paths without Link's type checking** — `` catches errors at compile time - **Don't put heavy logic in components** — loaders run before render and enable preloading/parallel fetching ## Key Patterns ```tsx // Auth guard with beforeLoad + redirect export const Route = createFileRoute('/_auth')({ beforeLoad: ({ context }) => { if (!context.auth.user) { throw redirect({ to: '/login', search: { redirect: location.href } }) } }, }) // Search params with Standard Schema (no adapter needed) import { z } from 'zod' const searchSchema = z.object({ page: z.number().default(1), sort: z.enum(['newest', 'oldest']).default('newest'), }) export const Route = createFileRoute('/posts')({ validateSearch: searchSchema, // Pass schema directly }) // Loader with ensureQueryData export const Route = createFileRoute('/posts/$postId')({ loader: ({ context, params }) => context.queryClient.ensureQueryData(postQueryOptions(params.postId)), component: PostComponent, }) function PostComponent() { const post = Route.useLoaderData() return

{post.title}

} // Code-split with .lazy.tsx // posts.tsx — keeps loader (critical path) export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), }) // posts.lazy.tsx — splits component (loaded after) export const Route = createLazyFileRoute('/posts')({ component: PostsComponent, }) ```