tanstack-router-migration
Migrates React Router codebases to TanStack Router with file-based routing.
Install
mkdir -p .claude/skills/tanstack-router-migration && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4056" && unzip -o skill.zip -d .claude/skills/tanstack-router-migration && rm skill.zipInstalls to .claude/skills/tanstack-router-migration
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Migrate React applications from React Router to TanStack Router with file-based routing. Use when user requests: (1) Router migration, (2) TanStack Router setup, (3) File-based routing implementation, (4) React Router replacement, (5) Type-safe routing, or mentions 'migrate router', 'tanstack router', 'file-based routes'.Key capabilities
- →Implement file-based routing in React
- →Configure type-safe route navigation
- →Validate search parameters with Zod schemas
- →Register router types for TypeScript inference
- →Migrate React Router hooks to TanStack Router hooks
How it works
It replaces React Router with a file-based routing system that uses a build plugin to generate type-safe route trees and Zod-validated search parameters.
Inputs & outputs
When to use tanstack-router-migration
- →Switch to file-based routing
- →Implement type-safe router
- →Replace React Router with TanStack
About this skill
React Router to TanStack Router Migration
Migrate React applications from React Router to TanStack Router with file-based routing. This skill provides a structured approach for both incremental and clean migrations.
Critical Rules
ALWAYS:
- Use file-based routing with routes in
src/routes/directory - Use
fromparameter in all hooks for type safety (useParams({ from: '/path' })) - Validate search params with Zod schemas using
@tanstack/zod-adapter - Configure build tool plugin before creating routes
- Register router type for full TypeScript inference
- Use
fallback()wrapper for optional search params
NEVER:
- Edit
routeTree.gen.ts(auto-generated file) - Use React Router hooks in new code during migration
- Forget the
fromparameter (loses type safety) - Use string-only validation for search params
- Skip the build plugin configuration
Dependencies
# Core dependencies
bun add @tanstack/react-router @tanstack/zod-adapter
# Build plugin (choose one based on your bundler)
bun add -d @tanstack/router-plugin
# Optional integrations
bun add nuqs # URL state management
bun add @sentry/react # Error tracking with router integration
Migration Phases
Phase 1: Assessment
Audit existing React Router usage:
# Find all React Router imports
grep -r "from 'react-router" src/ --include="*.tsx" --include="*.ts"
grep -r 'from "react-router' src/ --include="*.tsx" --include="*.ts"
# Find hook usages
grep -r "useParams\|useSearchParams\|useNavigate\|useLocation\|useMatch" src/
Document:
- React Router version (v5 or v6)
- Number of routes
-
useParamsusage count -
useSearchParamsusage count -
useNavigateusage count - Custom Link components
- Route guards/protected routes
- Existing route structure
Phase 2: Setup
1. Configure Build Tool
See references/build-configuration.md for full configs.
Rspack/Rsbuild:
// rsbuild.config.ts
import { TanStackRouterRspack } from '@tanstack/router-plugin/rspack';
export default {
tools: {
rspack: (config) => {
config.plugins?.push(
TanStackRouterRspack({
target: 'react',
autoCodeSplitting: true,
routesDirectory: './src/routes',
generatedRouteTree: './src/routeTree.gen.ts',
quoteStyle: 'single',
semicolons: true,
})
);
// Prevent rebuild loop
config.watchOptions = { ignored: ['**/routeTree.gen.ts'] };
return config;
},
},
};
Vite:
// vite.config.ts
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
export default defineConfig({
plugins: [
TanStackRouterVite({
target: 'react',
autoCodeSplitting: true,
routesDirectory: './src/routes',
generatedRouteTree: './src/routeTree.gen.ts',
}),
react(),
],
});
2. Configure Linter
// biome.jsonc or eslint config
{
"files": {
"ignore": ["**/routeTree.gen.ts"]
},
"overrides": [
{
"include": ["**/routes/**/*"],
"linter": {
"rules": {
"style": {
"useFilenamingConvention": "off" // Allow $param.tsx naming
}
}
}
}
]
}
3. Create Routes Directory
mkdir -p src/routes
Phase 3: Router Creation
Create Router Instance:
// src/app.tsx
import { createRouter, RouterProvider } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { routeTree } from './routeTree.gen';
import { NotFoundPage } from './components/misc/not-found-page';
const queryClient = new QueryClient();
const router = createRouter({
routeTree,
context: {
basePath: getBasePath(),
queryClient,
},
basepath: getBasePath(),
trailingSlash: 'never',
defaultNotFoundComponent: NotFoundPage,
});
// Register router type for full TypeScript inference
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
// Extend HistoryState for typed navigation state
interface HistoryState {
// Add your custom state properties here
returnUrl?: string;
documentId?: string;
documentName?: string;
}
}
export function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}
Define Router Context Type:
// src/routes/__root.tsx
import type { QueryClient } from '@tanstack/react-query';
export type RouterContext = {
basePath: string;
queryClient: QueryClient;
};
Phase 4: Route Migration
Create Root Layout:
// src/routes/__root.tsx
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
import type { QueryClient } from '@tanstack/react-query';
import { NuqsAdapter } from 'nuqs/adapters/tanstack-router';
export type RouterContext = {
basePath: string;
queryClient: QueryClient;
};
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout,
});
function RootLayout() {
return (
<>
<NuqsAdapter>
<ErrorBoundary>
<AppLayout>
<Outlet />
</AppLayout>
</ErrorBoundary>
</NuqsAdapter>
{process.env.NODE_ENV === 'development' && (
<TanStackRouterDevtools position="bottom-right" />
)}
</>
);
}
File-Based Route Structure:
src/routes/
├── __root.tsx # Root layout
├── index.tsx # / (root redirect)
├── overview/
│ └── index.tsx # /overview
├── topics/
│ ├── index.tsx # /topics
│ └── $topicName/
│ ├── index.tsx # /topics/:topicName
│ └── edit.tsx # /topics/:topicName/edit
├── security/
│ ├── index.tsx # /security (redirect)
│ ├── acls/
│ │ ├── index.tsx # /security/acls
│ │ ├── create.tsx # /security/acls/create
│ │ └── $aclName/
│ │ └── details.tsx # /security/acls/:aclName/details
See references/route-templates.md for complete templates.
Phase 5: Hook Migration
| React Router | TanStack Router |
|---|---|
useParams() | useParams({ from: '/path/$param' }) |
useSearchParams() | routeApi.useSearch() with Zod validation |
useNavigate() | useNavigate({ from: '/path' }) |
useLocation() | useLocation() (same API) |
<Link to="/path"> | <Link to="/path"> (type-safe) |
<Navigate to="/path" /> | <Navigate to="/path" /> |
See references/migration-patterns.md for detailed before/after examples.
Navigation State:
Pass typed state between routes using HistoryState:
// Navigating with state
const navigate = useNavigate();
navigate({
to: '/documents/$documentId',
params: { documentId },
state: {
returnUrl: location.pathname,
documentName: 'My Document',
},
});
// Reading state in destination component
import { useLocation } from '@tanstack/react-router';
function DocumentPage() {
const location = useLocation();
const { returnUrl, documentName } = location.state;
// Use state values...
}
useParams Migration:
// Before (React Router)
import { useParams } from 'react-router-dom';
const { id } = useParams<{ id: string }>();
// After (TanStack Router)
import { useParams } from '@tanstack/react-router';
const { id } = useParams({ from: '/items/$id' });
useSearch with Zod Validation:
// In route file
import { fallback, zodValidator } from '@tanstack/zod-adapter';
import { z } from 'zod';
const searchSchema = z.object({
tab: fallback(z.string().optional(), undefined),
page: fallback(z.number().optional(), 1),
q: fallback(z.string().optional(), undefined),
});
export const Route = createFileRoute('/items/')({
validateSearch: zodValidator(searchSchema),
component: ItemsPage,
});
// In component
import { getRouteApi, useNavigate } from '@tanstack/react-router';
const routeApi = getRouteApi('/items/');
function ItemsPage() {
const { tab, page, q } = routeApi.useSearch();
const navigate = useNavigate({ from: '/items/' });
const handleTabChange = (newTab: string) => {
navigate({ search: (prev) => ({ ...prev, tab: newTab }) });
};
}
Phase 6: Testing
Create Test Utilities:
// src/test-utils.tsx
import { createMemoryHistory, createRouter, RouterProvider } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, type RenderOptions } from '@testing-library/react';
import { routeTree } from './routeTree.gen';
import type { RouterContext } from './routes/__root';
interface RenderWithFileRoutesOptions extends Omit<RenderOptions, 'wrapper'> {
initialLocation?: string;
routerContext?: Partial<RouterContext>;
}
export function renderWithFileRoutes(
ui: React.ReactElement | null = null,
{ initialLocation = '/', routerContext = {}, ...renderOptions }: RenderWithFileRoutesOptions = {}
) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const router = createRouter({
routeTree,
history: createMemoryHistory({ initialEntries: [initialLocation] }),
context: { basePath: '', queryClient, ...routerContext },
});
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router}>{children}</RouterProvider>
</QueryClientProvider>
);
}
return {
...render(ui ?? <div />, { wrapper: Wrapper, ...renderOptions }),
---
*Content truncated.*
When not to use it
- →When editing the auto-generated routeTree.gen.ts file
- →When using React Router hooks in new code
Prerequisites
Limitations
- →Requires build tool plugin configuration
- →Requires migration of all legacy hooks to TanStack equivalents
How it compares
It enforces type safety across the entire routing layer, whereas React Router relies on string-based paths and manual type definitions.
Compared to similar skills
tanstack-router-migration side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| tanstack-router-migration (this skill) | 1 | 7mo | Review | Advanced |
| typescript | 28 | 2mo | No flags | Beginner |
| react-patterns | 9 | 6mo | No flags | Intermediate |
| typescript-skills | 3 | 6mo | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by redpanda-data
View all by redpanda-data →You might also like
typescript
lobehub
TypeScript code style and optimization guidelines. Use when writing TypeScript code (.ts, .tsx, .mts files), reviewing code quality, or implementing type-safe patterns. Triggers on TypeScript development, type safety questions, or code style discussions.
react-patterns
davila7
Modern React patterns and principles. Hooks, composition, performance, TypeScript best practices.
typescript-skills
llama-farm
Shared TypeScript best practices for Designer and Electron subsystems.
code-standards
redpanda-data
TypeScript, React, and JavaScript best practices enforced by Ultracite/Biome.
js-tosorted-immutable
TheOrcDev
Use toSorted() instead of sort() to avoid mutating arrays. Apply when sorting arrays that are React props, state, or otherwise shared/referenced elsewhere.
writing-react-effects
dust-tt
Writes React components without unnecessary useEffect. Use when creating/reviewing React components, refactoring effects, or when code uses useEffect to transform data or handle events.