TanStack Router patterns for type-safe, file-based routing. Covers installation, route configuration, typed params/search, layouts, and navigation. Use when setting up routes, implementing navigation, or configuring route loaders.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
skills listSkill Instructions
name: tanstack-router description: TanStack Router patterns for type-safe, file-based routing. Covers installation, route configuration, typed params/search, layouts, and navigation. Use when setting up routes, implementing navigation, or configuring route loaders.
TanStack Router Patterns
Type-safe, file-based routing for React applications with TanStack Router.
Installation
pnpm add @tanstack/react-router
pnpm add -D @tanstack/router-plugin
// vite.config.ts
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react(),
TanStackRouterVite(), // Generates route tree
],
})
Bootstrap
// 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 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 Routes
src/routes/
├── __root.tsx # Root layout (Outlet, providers)
├── index.tsx # "/" route
├── about.tsx # "/about" route
├── users/
│ ├── index.tsx # "/users" route
│ └── $userId.tsx # "/users/:userId" route (dynamic)
└── posts/
├── $postId/
│ ├── index.tsx # "/posts/:postId" route
│ └── edit.tsx # "/posts/:postId/edit" route
└── index.tsx # "/posts" route
Naming Conventions:
__root.tsx- Root layout (contains<Outlet />)index.tsx- Index route for that path$param.tsx- Dynamic parameter (e.g.,$userId→:userId)_layout.tsx- Layout route (no URL segment)route.lazy.tsx- Lazy-loaded route
Root Layout
// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
export const Route = createRootRoute({
component: () => (
<>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users">Users</Link>
</nav>
<main>
<Outlet /> {/* Child routes render here */}
</main>
<TanStackRouterDevtools /> {/* Auto-hides in production */}
</>
),
})
Basic Route
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutComponent,
})
function AboutComponent() {
return <div>About Page</div>
}
Dynamic Routes with Params
// src/routes/users/$userId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/users/$userId')({
component: UserComponent,
})
function UserComponent() {
const { userId } = Route.useParams() // Fully typed!
return <div>User ID: {userId}</div>
}
Typed Search Params
// src/routes/users/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const userSearchSchema = z.object({
page: z.number().default(1),
filter: z.enum(['active', 'inactive', 'all']).default('all'),
search: z.string().optional(),
})
export const Route = createFileRoute('/users/')({
validateSearch: userSearchSchema,
component: UsersComponent,
})
function UsersComponent() {
const { page, filter, search } = Route.useSearch() // Fully typed!
return (
<div>
<p>Page: {page}</p>
<p>Filter: {filter}</p>
{search && <p>Search: {search}</p>}
</div>
)
}
Navigation with Link
import { Link } from '@tanstack/react-router'
// Basic navigation
<Link to="/about">About</Link>
// With params
<Link to="/users/$userId" params={{ userId: '123' }}>
View User
</Link>
// With search params
<Link
to="/users"
search={{ page: 2, filter: 'active' }}
>
Users Page 2
</Link>
// With state
<Link to="/details" state={{ from: 'home' }}>
Details
</Link>
// Active link styling
<Link
to="/about"
activeProps={{ className: 'text-blue-600 font-bold' }}
inactiveProps={{ className: 'text-gray-600' }}
>
About
</Link>
Programmatic Navigation
import { useNavigate } from '@tanstack/react-router'
function MyComponent() {
const navigate = useNavigate()
const handleClick = () => {
// Navigate to route
navigate({ to: '/users' })
// With params
navigate({ to: '/users/$userId', params: { userId: '123' } })
// With search
navigate({ to: '/users', search: { page: 2 } })
// Replace history
navigate({ to: '/login', replace: true })
// Go back
navigate({ to: '..' }) // Relative navigation
}
return <button onClick={handleClick}>Navigate</button>
}
Route Loaders (Data Fetching)
Basic Loader:
// src/routes/users/$userId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/users/$userId')({
loader: async ({ params }) => {
const user = await fetchUser(params.userId)
return { user }
},
component: UserComponent,
})
function UserComponent() {
const { user } = Route.useLoaderData() // Fully typed!
return <div>{user.name}</div>
}
With TanStack Query Integration (see router-query-integration skill for details):
import { queryClient } from '@/app/queryClient'
import { userQuery Options } from '@/features/users/queries'
export const Route = createFileRoute('/users/$userId')({
loader: ({ params }) =>
queryClient.ensureQueryData(userQueryOptions(params.userId)),
component: UserComponent,
})
Layouts
Layout Route (_layout.tsx - no URL segment):
// src/routes/_layout.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_layout')({
component: LayoutComponent,
})
function LayoutComponent() {
return (
<div className="dashboard-layout">
<Sidebar />
<div className="content">
<Outlet /> {/* Child routes */}
</div>
</div>
)
}
// Child routes
// src/routes/_layout/dashboard.tsx → "/dashboard"
// src/routes/_layout/settings.tsx → "/settings"
Loading States
export const Route = createFileRoute('/users')({
loader: async () => {
const users = await fetchUsers()
return { users }
},
pendingComponent: () => <Spinner />,
errorComponent: ({ error }) => <ErrorMessage>{error.message}</ErrorMessage>,
component: UsersComponent,
})
Error Handling
import { ErrorComponent } from '@tanstack/react-router'
export const Route = createFileRoute('/users')({
loader: async () => {
const users = await fetchUsers()
if (!users) throw new Error('Failed to load users')
return { users }
},
errorComponent: ({ error, reset }) => (
<div>
<h1>Error loading users</h1>
<p>{error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
),
component: UsersComponent,
})
Route Context
Providing Context:
// src/routes/__root.tsx
export const Route = createRootRoute({
beforeLoad: () => ({
user: getCurrentUser(),
}),
component: RootComponent,
})
// Access in child routes
export const Route = createFileRoute('/dashboard')({
component: function Dashboard() {
const { user } = Route.useRouteContext()
return <div>Welcome, {user.name}</div>
},
})
Route Guards / Auth
// src/routes/_authenticated.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context }) => {
if (!context.user) {
throw redirect({ to: '/login' })
}
},
component: Outlet,
})
// Protected routes
// src/routes/_authenticated/dashboard.tsx
// src/routes/_authenticated/profile.tsx
Preloading
Hover Preload:
<Link
to="/users/$userId"
params={{ userId: '123' }}
preload="intent" // Preload on hover
>
View User
</Link>
Options:
preload="intent"- Preload on hover/focuspreload="render"- Preload when link renderspreload={false}- No preload (default)
DevTools
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
// Add to root layout
<TanStackRouterDevtools position="bottom-right" />
Auto-hides in production builds.
Best Practices
- Use Type-Safe Navigation - Let TypeScript catch routing errors at compile time
- Validate Search Params - Use Zod schemas for search params
- Prefetch Data in Loaders - Integrate with TanStack Query for optimal data fetching
- Use Layouts for Shared UI - Avoid duplicating layout code across routes
- Lazy Load Routes - Use
route.lazy.tsxfor code splitting - Leverage Route Context - Share data down the route tree efficiently
Common Patterns
Catch-All Route:
// src/routes/$.tsx
export const Route = createFileRoute('/$')({
component: () => <div>404 Not Found</div>,
})
Optional Params:
// Use search params for optional data
const searchSchema = z.object({
optional: z.string().optional(),
})
Multi-Level Dynamic Routes:
/posts/$postId/comments/$commentId
Related Skills
- tanstack-query - Data fetching and caching
- router-query-integration - Integrating Router loaders with Query
- core-principles - Project structure with routes
More by MadAppGang
View allChoose optimal external AI models for code analysis, bug investigation, and architectural decisions. Use when consulting multiple LLMs via claudish, comparing model perspectives, or investigating complex Go/LSP/transpiler issues. Provides empirically validated model rankings (91/100 for MiniMax M2, 83/100 for Grok Code Fast) and proven consultation strategies based on real-world testing.
CRITICAL - Guide for using Claudish CLI ONLY through sub-agents to run Claude Code with OpenRouter models (Grok, GPT-5, Gemini, MiniMax). NEVER run Claudish directly in main context unless user explicitly requests it. Use when user mentions external AI models, Claudish, OpenRouter, or alternative models. Includes mandatory sub-agent delegation patterns, agent selection guide, file-based instructions, and strict rules to prevent context window pollution.
MANDATORY tracking protocol for multi-model validation. Creates structured tracking tables BEFORE launching models, tracks progress during execution, and ensures complete results presentation. Use when running 2+ external AI models in parallel. Trigger keywords - "multi-model", "parallel review", "external models", "consensus", "model tracking".
XML tag structure patterns for Claude Code agents and commands. Use when designing or implementing agents to ensure proper XML structure following Anthropic best practices.