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
آخر نشاط في المصدر
١٦ سبتمبر ٢٠٢٦ في ١٤:٠٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٤٬٣٤٧
التفرعات
٤٣٣

خيارات التثبيت

يُحدَّد 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