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.
/plugin marketplace add jezweb/claude-skills/plugin install jezweb-tooling-skills@jezweb/claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
README.mdreferences/common-errors.mdrules/tanstack-router.mdtemplates/package.jsontemplates/route-examples.tsxtemplates/vite.config.tsType-safe, file-based routing for React SPAs with route-level data loading and TanStack Query integration
Last Updated: 2026-01-09 Version: @tanstack/react-router@1.146.2
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} />
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>
},
})
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.
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.
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>,
})
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
/>
)
}
Issue #1: Devtools Dependency Resolution
@tanstack/router-devtools-core not foundnpm install @tanstack/router-devtoolsIssue #2: Vite Plugin Order (CRITICAL)
routeTree.gen.ts missingIssue #3: Type Registration Missing
<Link to="..."> not typed, no autocompleterouteTree from ./routeTree.gen in main.tsx to register typesIssue #4: Loader Not Running
Route constant: export const Route = createFileRoute('/path')({ loader: ... })Issue #5: Memory Leak with TanStack Form (FIXED)
Issue #6: Virtual Routes Index/Layout Conflict
physical() in virtual routing_layout.tsx + _layout.index.tsxIssue #7: Search Params Type Inference
zodSearchValidatorzodValidator from @tanstack/zod-adapter insteadIssue #8: TanStack Start Validators on Reload
validateSearch not working on page reload in TanStack StartVite 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)
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.