router-query/repository-context/docs/router/how-to/migrate-from-react-router.md
Version 663282b0ebbc.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.
← Return to resource and package checksum
title: How to Migrate from React Router v7
This guide provides a step-by-step process to migrate your application from React Router v7 to TanStack Router. We'll cover the complete migration process from removing React Router dependencies to implementing TanStack Router's type-safe routing patterns.
Quick Start
Time Required: 2-4 hours depending on app complexity
Difficulty: Intermediate
Prerequisites: Basic React knowledge, existing React Router v7 app
What You'll Accomplish
- Remove React Router v7 dependencies and components
- Install and configure TanStack Router
- Convert route definitions to file-based routing
- Update navigation components and hooks
- Implement type-safe routing patterns
- Handle search params and dynamic routes
- Migrate from React Router v7's new features to TanStack Router equivalents
Complete Migration Process
Step 1: Prepare for Migration
Before making any changes, prepare your environment and codebase:
1.1 Create a backup branch
git checkout -b migrate-to-tanstack-router
git push -u origin migrate-to-tanstack-router
1.2 Install TanStack Router (keep React Router temporarily)
# Install TanStack Router
npm install @tanstack/react-router
# Install development dependencies
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools
1.3 Set up the router plugin for your bundler
For Vite users, update your 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(), // Add this before react plugin
react(),
],
})
For other bundlers, see our bundler configuration guides.
Step 2: Create TanStack Router Configuration
2.1 Create router configuration file
Create tsr.config.json in your project root:
{
"routesDirectory": "./src/routes",
"generatedRouteTree": "./src/routeTree.gen.ts",
"quoteStyle": "single"
}
2.2 Create routes directory
mkdir src/routes
Step 3: Convert Your React Router v7 Structure
3.1 Identify your current React Router v7 setup
React Router v7 introduced several new patterns. Look for:
createBrowserRouterwith new data APIs- Framework mode configurations
- Server-side rendering setup
- New
loaderandactionfunctions deferusage (simplified in v7)- Type-safe routing features
3.2 Create root route
Create src/routes/__root.tsx:
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: () => (
<>
{/* Your existing layout/navbar content */}
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
),
})
3.3 Create index route
Create src/routes/index.tsx for your home page:
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: Index,
})
function Index() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
}
3.4 Convert React Router v7 loaders
React Router v7 simplified loader patterns. Here's how to migrate them:
React Router v7:
// app/routes/posts.tsx
export async function loader() {
const posts = await fetchPosts()
return { posts } // v7 removed need for json() wrapper
}
export default function Posts() {
const { posts } = useLoaderData()
return <div>{/* render posts */}</div>
}
TanStack Router equivalent:
Create src/routes/posts.tsx:
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
loader: async () => {
const posts = await fetchPosts()
return { posts }
},
component: Posts,
})
function Posts() {
const { posts } = Route.useLoaderData()
return <div>{/* render posts */}</div>
}
3.5 Convert dynamic routes
React Router v7:
// app/routes/posts.$postId.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 equivalent:
Create 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: Post,
})
function Post() {
const { post } = Route.useLoaderData()
const { postId } = Route.useParams()
return <div>{post.title}</div>
}
3.6 Convert React Router v7 actions
React Router v7:
export async function action({ request, params }) {
const formData = await request.formData()
const result = await updatePost(params.postId, formData)
return { success: true }
}
TanStack Router equivalent:
export const Route = createFileRoute('/posts/$postId/edit')({
component: EditPost,
// Actions are typically handled differently in TanStack Router
// Use mutations or form libraries like React Hook Form
})
function EditPost() {
const navigate = useNavigate()
const handleSubmit = async (formData) => {
const result = await updatePost(params.postId, formData)
navigate({ to: '/posts/$postId', params: { postId } })
}
return <form onSubmit={handleSubmit}>{/* form */}</form>
}
Step 4: Handle React Router v7 Framework Features
4.1 Server-Side Rendering Migration
React Router v7 introduced framework mode with SSR. If you're using this:
React Router v7 Framework Mode:
// react-router.config.ts
export default {
ssr: true,
prerender: ['/'],
}
TanStack Router approach:
TanStack Router has built-in SSR capabilities. Set up your router for SSR:
// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({
routeTree,
context: {
// Add any SSR context here
},
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
export { router }
For server-side rendering, use TanStack Router's built-in SSR APIs:
// server.tsx
import { createMemoryHistory } from '@tanstack/react-router'
import { StartServer } from '@tanstack/start/server'
export async function render(url: string) {
const router = createRouter({
routeTree,
history: createMemoryHistory({ initialEntries: [url] }),
})
await router.load()
return (
<StartServer router={router} />
)
}
4.2 Code Splitting Migration
React Router v7 improved code splitting. TanStack Router handles this via lazy routes:
React Router v7:
const LazyComponent = lazy(() => import('./LazyComponent'))
TanStack Router:
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/lazy-route')({
component: LazyComponent,
})
function LazyComponent() {
return <div>Lazy loaded!</div>
}
Step 5: Update Navigation Components
5.1 Update Link components
React Router v7:
import { Link } from 'react-router'
<Link to="/posts/123">View Post</Link>
<Link to="/posts" state={{ from: 'home' }}>Posts</Link>
TanStack Router:
import { Link } from '@tanstack/react-router'
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
<Link to="/posts" state={{ from: 'home' }}>Posts</Link>
5.2 Update navigation hooks
React Router v7:
import { useNavigate } from 'react-router'
function Component() {
const navigate = useNavigate()
const handleClick = () => {
navigate('/posts/123')
}
}
TanStack Router:
import { useNavigate } from '@tanstack/react-router'
function Component() {
const navigate = useNavigate()
const handleClick = () => {
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
}
Step 6: Handle React Router v7 Specific Features
6.1 Migrate simplified defer usage
React Router v7 simplified defer by removing the wrapper function:
React Router v7:
export async function loader() {
return {
data: fetchData(), // Promise directly returned
}
}
TanStack Router:
TanStack Router uses a different approach for deferred data. Use loading states:
export const Route = createFileRoute('/deferred')({
loader: async () => {
const data = await fetchData()
return { data }
},
pendingComponent: () => <div>Loading...</div>,
component: DeferredComponent,
})
6.2 Handle React Router v7's enhanced type safety
React Router v7 improved type inference. TanStack Router provides even better type safety:
// TanStack Router automatically infers types
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
// params.postId is automatically typed as string
const post = await fetchPost(params.postId)
return { post }
},
component: Post,
})
function Post() {
// post is automatically typed based on loader return
const { post } = Route.useLoaderData()
// postId is automatically typed as string
const { postId } = Route.useParams()
}
Step 7: Update Your Main Router Setup
7.1 Replace React Router v7 router creation
Before (React Router v7):
import { createBrowserRouter, RouterProvider } from 'react-router'
const router = createBrowserRouter([
// Your route definitions
])
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
After (TanStack Router):
import { RouterProvider } from '@tanstack/react-router'
import { router } from './router'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
Step 8: Handle Search Parameters
8.1 React Router v7 to TanStack Router search params
React Router v7:
import { useSearchParams } from 'react-router'
function Component() {
const [searchParams, setSearchParams] = useSearchParams()
const page = searchParams.get('page') || '1'
const updatePage = (newPage) => {
setSearchParams({ page: newPage })
}
}
TanStack Router:
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const searchSchema = z.object({
page: z.number().catch(1),
filter: z.string().optional(),
})
export const Route = createFileRoute('/posts')({
validateSearch: searchSchema,
component: Posts,
})
function Posts() {
const navigate = useNavigate({ from: '/posts' })
const { page, filter } = Route.useSearch()
const updatePage = (newPage: number) => {
navigate({ search: (prev) => ({ ...prev, page: newPage }) })
}
}
Step 9: Remove React Router Dependencies
Only after everything is working with TanStack Router:
9.1 Remove React Router v7
npm uninstall react-router
9.2 Clean up unused imports
Search your codebase for any remaining React Router imports:
# Find remaining React Router imports
grep -r "react-router" src/
Remove any remaining imports and replace with TanStack Router equivalents.
Step 10: Add Advanced Type Safety
10.1 Configure strict TypeScript
Update your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}
10.2 Add search parameter validation
For routes with search parameters, add validation schemas:
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const postsSearchSchema = z.object({
page: z.number().min(1).catch(1),
search: z.string().optional(),
category: z.enum(['tech', 'business', 'lifestyle']).optional(),
})
export const Route = createFileRoute('/posts')({
validateSearch: postsSearchSchema,
component: Posts,
})
Production Checklist
Before deploying your migrated application:
Router Configuration
- [ ] Router instance created and properly exported
- [ ] Route tree generated successfully
- [ ] TypeScript declarations registered
- [ ] All route files follow naming conventions
Route Migration
- [ ] All React Router v7 routes converted to file-based routing
- [ ] Dynamic routes updated with proper parameter syntax
- [ ] Nested routes maintain hierarchy
- [ ] Index routes created where needed
- [ ] Layout routes preserve component structure
Feature Migration
- [ ] All React Router v7 loaders converted
- [ ] Actions migrated to appropriate patterns
- [ ] Server-side rendering configured (if applicable)
- [ ] Code splitting implemented
- [ ] Type safety enhanced
Navigation Updates
- [ ] All Link components updated to TanStack Router
- [ ] useNavigate hooks replaced and tested
- [ ] Navigation parameters properly typed
- [ ] Search parameter validation implemented
Code Cleanup
- [ ] React Router v7 dependencies removed
- [ ] Unused imports cleaned up
- [ ] No React Router references remain
- [ ] TypeScript compilation successful
- [ ] All tests passing
Testing
- [ ] All routes accessible and rendering correctly
- [ ] Navigation between routes working
- [ ] Back/forward browser buttons functional
- [ ] Search parameters persisting correctly
- [ ] Dynamic routes with parameters working
- [ ] Nested route layouts displaying properly
- [ ] Framework features (SSR, code splitting) working if applicable
Common Problems
Error: "Cannot use useNavigate outside of context"
Problem: You have remaining React Router imports that conflict with TanStack Router.
Solution:
- Search for all React Router imports:
bash grep -r "react-router" src/ - Replace all imports with TanStack Router equivalents
- Ensure React Router is completely uninstalled
TypeScript Errors: Route Parameters
Problem: TypeScript showing errors about route parameters not being typed correctly.
Solution:
- Ensure your router is registered in the TypeScript module declaration:
typescript declare module '@tanstack/react-router' { interface Register { router: typeof router } } - Check that your route files export the Route correctly
- Verify parameter names match between route definition and usage
React Router v7 Framework Features Not Working
Problem: Missing SSR or code splitting functionality after migration.
Solution:
- TanStack Router has built-in SSR capabilities - use TanStack Start for full-stack applications
- Use TanStack Router's lazy routes for code splitting
- Configure SSR using TanStack Router's native APIs
- Follow the SSR setup guide for detailed instructions
Routes Not Matching
Problem: Routes not rendering or 404 errors for valid routes.
Solution:
- Check file naming follows TanStack Router conventions:
- Dynamic routes:
$paramName.tsx - Index routes:
index.tsx - Nested routes: proper directory structure
- Verify route tree generation is working
- Check that the router plugin is properly configured
React Router v7 Simplified APIs Not Translating
Problem: v7's simplified defer or other features don't have direct equivalents.
Solution:
- Use TanStack Router's pending states for loading UX
- Implement data fetching patterns that fit TanStack Router's architecture
- Leverage TanStack Router's superior type safety for better DX
React Router v7 vs TanStack Router Feature Comparison
| Feature | React Router v7 | TanStack Router |
|---|---|---|
| Type Safety | Good | Excellent |
| File-based Routing | Framework mode only | Built-in |
| Search Params | Basic | Validated with schemas |
| Code Splitting | Good | Excellent with lazy routes |
| SSR | Framework mode | Built-in with TanStack Start |
| Bundle Size | Larger | Smaller |
| Learning Curve | Moderate | Moderate |
| Community | Large | Growing |
Common Next Steps
After successfully migrating to TanStack Router, consider these enhancements:
Advanced Features to Explore
- Route-based code splitting - Improve performance with lazy loading
- Search parameter validation - Type-safe URL state management
- Route preloading - Enhance perceived performance
- Route masking - Advanced URL management
- Integration with TanStack Query - Powerful data fetching
Related Resources
- TanStack Router Documentation - Complete API reference
- File-Based Routing Guide - Detailed routing concepts
- Navigation Guide - Complete navigation patterns
- Search Parameters Guide - Advanced search param usage
- Type Safety Guide - TypeScript integration details
- React Router v7 Changelog - What changed in v7