Skip to main content

migrate-from-react-router

Step-by-step migration from React Router v7 to TanStack Router: route definition conversion, Link/useNavigate API differences, useSearchParams to validateSearch + useSearch, useParams with from, Outlet replacement, loader conversion, code splitting differences.

Quellinformationen

Repository
TanStack/router
Letzte Quellaktivität
26. Juli 2026 um 16:53
Erkannte Sprache von SKILL.md
Englisch
Sterne
15.202
Forks
1.900

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
migrate-from-react-router
description
Step-by-step migration from React Router v7 to TanStack Router: route definition conversion, Link/useNavigate API differences, useSearchParams to validateSearch + useSearch, useParams with from, Outlet replacement, loader conversion, code splitting differences.
metadata
{"type":"lifecycle","library":"tanstack-router","library_version":"1.166.2"}
requires
["router-core","react-router"]
sources
["TanStack/router:docs/router/how-to/migrate-from-react-router.md","TanStack/router:docs/router/installation/migrate-from-react-router.md"]
# Migrate from React Router v7 to TanStack Router This is a step-by-step migration checklist. Each check covers one conversion task. Complete them in order. > **CRITICAL**: If your UI is blank after migration, open the console. Errors like "cannot use useNavigate outside of context" mean React Router imports remain alongside TanStack Router imports. Uninstall `react-router` (and `react-router-dom` if present) to surface them as TypeScript errors. > > **CRITICAL**: TanStack Router uses `to` + `params` for navigation, NOT template literal paths. Never interpolate params into the `to` string. > > **NOTE**: React Router v7 recommends importing from `react-router` (not `react-router-dom`). The `react-router-dom` package still exists but just re-exports from `react-router`. Check for imports from both. ## Pre-Migration - [ ] **Create a migration branch** ```bash git checkout -b migrate-to-tanstack-router ``` - [ ] **Install TanStack Router alongside React Router temporarily** ```bash npm install @tanstack/react-router @tanstack/react-router-devtools npm install -D @tanstack/router-plugin ``` - [ ] **Configure bundler plugin (Vite example)** ```ts // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { tanstackRouter } from '@tanstack/router-plugin/vite' export default defineConfig({ plugins: [ tanstackRouter({ target: 'react', autoCodeSplitting: true }), react(), ], }) ``` - [ ] **Create routes directory** ```bash mkdir src/routes ``` ## Route Definitions - [ ] **Create root route** React Router: `<BrowserRouter>` or `createBrowserRouter([{ element: <Layout />, children: [...] }])` TanStack Router: `src/routes/__root.tsx` ```tsx // src/routes/__root.tsx import { createRootRoute, Link, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' export const Route = createRootRoute({ component: () => ( <> <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> </nav> <Outlet /> <TanStackRouterDevtools /> </> ), }) ``` - [ ] **Create router instance with type registration** ```tsx // 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 }) declare module '@tanstack/react-router' { interface Register { router: typeof router } } const rootElement = document.getElementById('root')! if (!rootElement.innerHTML) { ReactDOM.createRoot(rootElement).render( <StrictMode> <RouterProvider router={router} /> </StrictMode>, ) } ``` - [ ] **Convert each route file to `createFileRoute`** React Router: ```tsx // Defined in route config array { path: '/posts', element: <Posts />, loader: postsLoader } ``` TanStack Router: ```tsx // src/routes/posts.tsx import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts')({ loader: async () => { const posts = await fetchPosts() return { posts } }, component: PostsPage, }) function PostsPage() { const { posts } = Route.useLoaderData() return ( <ul> {posts.map((p) => ( <li key={p.id}>{p.title}</li> ))} </ul> ) } ``` - [ ] **Convert dynamic routes** React Router: `/posts/:postId` (colon syntax) TanStack Router: `/posts/$postId` (dollar syntax) ```tsx // src/routes/posts/$postId.tsx import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { const post = await fetchPost(params.postId) return { post } }, component: PostPage, }) function PostPage() { const { post } = Route.useLoaderData() return <article>{post.title}</article> } ``` ## Navigation - [ ] **Convert all `<Link>` components** React Router: ```tsx import { Link } from 'react-router' ;<Link to={`/posts/${postId}`}>View Post</Link> ``` TanStack Router: ```tsx import { Link } from '@tanstack/react-router' ;<Link to="/posts/$postId" params={{ postId }}> View Post </Link> ``` Key differences: - `to` is a route path pattern, NOT an interpolated string - `params` is a separate prop with typed values - Active styling: use `activeProps={{ className: 'font-bold' }}` or `data-status="active"` attribute for CSS - [ ] **Convert all `useNavigate` calls** React Router: ```tsx import { useNavigate } from 'react-router' const navigate = useNavigate() navigate(`/posts/${postId}`) ``` TanStack Router: ```tsx import { useNavigate } from '@tanstack/react-router' const navigate = useNavigate() navigate({ to: '/posts/$postId', params: { postId } }) ``` ## Search Params - [ ] **Replace `useSearchParams` with `validateSearch` + `useSearch`** React Router: ```tsx import { useSearchParams } from 'react-router' function Posts() { const [searchParams, setSearchParams] = useSearchParams() const page = Number(searchParams.get('page')) || 1 const goToPage = (p: number) => setSearchParams({ page: String(p) }) } ``` TanStack Router: ```tsx // In the route definition: import { createFileRoute } from '@tanstack/react-router' import { z } from 'zod' export const Route = createFileRoute('/posts')({ validateSearch: z.object({ page: z.number().default(1).catch(1), }), component: Posts, }) // In the component: import { useNavigate } from '@tanstack/react-router' function Posts() { const { page } = Route.useSearch() const navigate = useNavigate({ from: '/posts' }) const goToPage = (p: number) => { navigate({ search: (prev) => ({ ...prev, page: p }) }) } } ``` Key differences: - Search params are validated and typed at the route level - `useSearch()` returns typed objects, not `URLSearchParams` - Update with function form to preserve other params ## Path Params - [ ] **Update `useParams` with `from` property** React Router: ```tsx import { useParams } from 'react-router' const { postId } = useParams() ``` TanStack Router: ```tsx import { useParams } from '@tanstack/react-router' const { postId } = useParams({ from: '/posts/$postId' }) ``` Or from within the route component: ```tsx const { postId } = Route.useParams() ``` ## `useLocation` — Common Pitfall - [ ] **Replace `useLocation` with specific hooks** React Router's `useLocation` is heavily used, and TanStack Router has a hook with the same name — but they are NOT equivalent. TanStack Router's `useLocation()` returns the router's current location, which during pending navigations may differ from what's currently rendered. Most React Router `useLocation` usage should be replaced with more specific hooks. See [#3110](https://github.com/TanStack/router/issues/3110). Replace based on what you actually need: ```tsx // React Router import { useLocation } from 'react-router' const location = useLocation() // ❌ DON'T just swap to TanStack Router's useLocation — it's the "live" URL import { useLocation } from '@tanstack/react-router' // ✅ DO use the specific hook for what you need: import { useMatch, useMatches, useParams, useSearch, } from '@tanstack/react-router' // Current route match (replaces most useLocation().pathname usage) const match = useMatch({ from: '/posts/$postId' }) // All active matches (replaces useLocation for breadcrumbs/analytics) const matches = useMatches() // Path params (replaces useLocation + manual parsing) const { postId } = useParams({ from: '/posts/$postId' }) // Search params (replaces useLocation().search parsing) const { page } = useSearch({ from: '/posts' }) ``` ## Outlet - [ ] **Replace React Router `Outlet` with TanStack Router `Outlet`** The API is identical — just change the import: ```tsx // Before import { Outlet } from 'react-router' // After import { Outlet } from '@tanstack/react-router' ``` ## Loaders - [ ] **Convert React Router loaders** React Router (v7): ```tsx export async function loader({ params }) { const post = await fetchPost(params.postId) return { post } } export default function Post() { const { post } = useLoaderData() return <div>{post.title}</div> } ``` TanStack Router: ```tsx export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { const post = await fetchPost(params.postId) return { post } }, component: Post, }) function Post() { const { post } = Route.useLoaderData() return <div>{post.title}</div> } ``` Key differences: - Loader is a route option, not a separate export - `useLoaderData()` is called via `Route.useLoaderData()` (or `useLoaderData({ from })`) - TanStack Router loaders run on the CLIENT by default (not server-only) - No `json()` wrapper needed — return plain objects ## Code Splitting - [ ] **Convert lazy route imports** — with `autoCodeSplitting: true` in the plugin config, this is automatic. For manual splitting, use `.lazy.tsx` files: ```tsx // src/routes/lazy-page.lazy.tsx import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/lazy-page')({ component: () => <div>Lazy loaded</div>, }) ``` ## Cleanup - [ ] **Remove React Router and verify** ```bash npm uninstall react-router react-router-dom grep -r "from 'react-router" src/ # find stale imports npx tsc --noEmit # verify clean build ``` - [ ] **Test all routes** — verify rendering, navigation (incl. back/forward), search params, dynamic params, and loaders ## Common Mistakes ### 1. HIGH: Leaving React Router imports alongside TanStack Router Both libraries export `Link`, `useNavigate`, `Outlet`, etc. Leftover React Router imports cause "cannot use useNavigate outside of context" errors because the wrong context provider is used. ```tsx // WRONG — mixed imports import { Link } from '@tanstack/react-router' import { useNavigate } from 'react-router' // <- still React Router! // CORRECT — all from TanStack Router import { Link, useNavigate } from '@tanstack/react-router' ``` **Fix**: Uninstall `react-router`/`react-router-dom` completely. TypeScript will flag every stale import. ### 2. HIGH: Using React Router `useSearchParams` pattern ```tsx // WRONG — React Router pattern, returns URLSearchParams const [searchParams, setSearchParams] = useSearchParams() const page = Number(searchParams.get('page')) // CORRECT — TanStack Router pattern, returns typed object // Route definition: validateSearch: z.object({ page: z.number().default(1).catch(1), }), // Component: const { page } = Route.useSearch() // page is already typed as number — no casting needed ```
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen