Skip to main content

tanstack-router-migration

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'.

来源信息

仓库
redpanda-data/console
最近来源活动
2026年9月16日 14:00
检测到的 SKILL.md 语言
英语
星标
4,336
分支
429

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
5 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
tanstack-router-migration
description
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'.
allowed-tools
Read, Write, Edit, Bash, Glob, Grep, Task
# 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 `from` parameter 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 `from` parameter (loses type safety) - Use string-only validation for search params - Skip the build plugin configuration ## Dependencies ```bash # 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:** ```bash # 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 - [ ] `useParams` usage count - [ ] `useSearchParams` usage count - [ ] `useNavigate` usage count - [ ] Custom Link components - [ ] Route guards/protected routes - [ ] Existing route structure ### Phase 2: Setup **1. Configure Build Tool** See [references/build-configuration.md](references/build-configuration.md) for full configs. **Rspack/Rsbuild:** ```typescript // 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:** ```typescript // 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** ```jsonc // 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** ```bash mkdir -p src/routes ``` ### Phase 3: Router Creation **Create Router Instance:** ```typescript // 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:** ```typescript // 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:** ```typescript // 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](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](references/migration-patterns.md) for detailed before/after examples. **Navigation State:** Pass typed state between routes using `HistoryState`: ```typescript // 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:** ```typescript // 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:** ```typescript // 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:** ```typescript // 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 }), router, }; } export async function renderRoute(location: string, options?: RenderWithFileRoutesOptions) { const result = renderWithFileRoutes(null, { initialLocation: location, ...options }); await result.router.load(); return result; } ``` **Configure Rstest:** ```typescript // rstest.config.integration.ts import { pluginReact } from '@rsbuild/plugin-react'; import { defineProject } from '@rstest/core'; export default defineProject({ plugins: [pluginReact()], include: ['src/**/*.test.tsx'], testEnvironment: { name: 'happy-dom',
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看