Migrating from React Router v5 to v6: Key Differences and New Features
This article provides a comprehensive guide for upgrading a React project from react‑router v5 to v6, detailing component replacements, route syntax changes, new features such as Routes, Outlet, useNavigate, and useSearchParams, and highlighting migration considerations and best practices.
Preface
Recently I finished a new company project that uses the latest tech stack, including React Router v6. I take this opportunity to review the differences between React Router v5 and v6 and the new features of v6. If your existing project still uses the older version, I do not recommend upgrading immediately because many changes may be required.
v5 to v6 Upgrade Guide
Replace <Switch> with <Routes>
v5
<BrowserRouter>
<Menu />
<Switch>
<Route component={Home} path="/home"></Route>
<Route component={List} path="/list"></Route>
<Route component={Detail} path="/detail"></Route>
<Route component={Category} path="/category"></Route>
</Switch>
</BrowserRouter>
// Category.tsx
<Switch>
<Route component={CategoryA} path="/category/a"></Route>
<Route component={CategoryB} path="/category/b"></Route>
</Switch>The Switch component renders the first child <Route> or <Redirect> that matches the location, rendering only a single route.
v6
<BrowserRouter>
<Menu />
<Routes>
<Route element={<Home />} path="/home"></Route>
<Route element={<List />} path="/list"></Route>
<Route element={<Detail />} path="/detail"></Route>
<Route element={<Category />} path="/category">
{/* children nested routes, path is relative */}
<Route element={<CategoryA />} path="a"></Route>
<Route element={<CategoryB />} path="b"></Route>
</Route>
</Routes>
</BrowserRouter>Compared with Switch , Routes offers several advantages:
All <Route> and <Link> inside Routes support relative paths (paths starting with / are absolute). This makes path and to more concise and predictable.
Route matching is based on the best path match rather than sequential traversal.
Routes can be nested in the same place without scattering components across the tree.
Note: Routes is not a drop‑in replacement for Switch . While Switch matches a single Route (or Redirect ) optionally, you can still use Route without Switch . However, when you use Route in v6, the surrounding Routes component becomes mandatory and must wrap the outermost routes, otherwise an error is thrown.
Route Component Props
In v5 the render or component prop is used; in v6 it is replaced by the element prop.
// v5
<Route component={Home} path="/home"></Route>
// v6
<Route element={<Home />} path="/home"></Route>Simplified path Format (only two dynamic placeholders)
:id – dynamic parameter.
* – wildcard, only allowed at the end of the path (e.g., users/* ).
Correct path examples in v6:
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*Incorrect path examples in v6:
/users/:id? // ? is not allowed
/tweets/:id(\d+) // regex not allowed
/files/*/cat.jpg
/files-*Case‑Sensitive Route Matching
The caseSensitive prop controls whether the path matching respects case. It is used on <Routes> or individual <Route> elements.
<Routes caseSensitive>New Outlet Component
Used to render nested child routes, similar to a slot.
export default function Category() {
return (
<div>
<div><Link to="a">Go to CategoryA</Link></div>
<div><Link to="b">Go to CategoryB</Link></div>
{/* Automatically render matched child route */}
<Outlet />
</div>
)
}Link Component Props – to Differences
In v5, a to value without a leading / is resolved relative to the current URL, which can be unpredictable. In v6, the to value is always resolved relative to the parent route, and trailing slashes are ignored, providing consistent behavior.
NavLink Changes
The exact prop is renamed to end .
activeStyle and activeClassName are removed; styling is now based on the isActive render prop.
<NavLink
to="/messages"
style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}
>
Messages
</NavLink>Redirect Component Removed
Redirect is removed because it is not SEO‑friendly. Use the Navigate component instead.
// v5
<Redirect from="/404" to="/home" />
// v6 (use Navigate)
<Route path="/404" element={<Navigate to="/home" replace />} />New useNavigate Hook (replaces useHistory )
Function components can now use useNavigate to obtain a navigation object for programmatic routing.
// v5
import { useHistory } from 'react-router-dom'
export default function Menu() {
const history = useHistory()
return (
<div>
<div onClick={() => { history.push('/list') }}>Navigate to list</div>
</div>
)
}
// v6
import { useNavigate } from 'react-router-dom'
export default function Menu() {
const navigate = useNavigate()
return (
<div>
<div onClick={() => { navigate('/list') }}>Navigate to list</div>
</div>
)
}Other differences:
// v5
history.replace('/list')
// v6
navigate('/list', { replace: true })
// v5
history.go(1)
history.go(-1)
// v6
navigate(1)
navigate(-1)New useRoutes Hook (replaces react-router-config )
useRoutes generates route elements from a route configuration array and must be used inside a <Router> component.
import { useRoutes } from 'react-router-dom'
import Home from './components/Home'
import List from './components/List'
function App() {
const element = useRoutes([
{ path: '/home', element: <Home /> },
{ path: '/list', element: <List /> },
])
return element
}
export default AppNew useSearchParams Hook
Provides a tuple to read and update URL query parameters.
import { useSearchParams } from 'react-router-dom'
export default function Detail() {
const [searchParams, setSearchParams] = useSearchParams()
console.log('getParams', searchParams.get('name'))
return (
<div onClick={() => { setSearchParams({ name: 'jacky' }) }}>
Detail page – click to set query param name=jacky
</div>
)
}<Prompt> Not Supported in v6
The old Prompt component that could block navigation is not available in v6. Consider alternative approaches if you need to warn users before leaving a page.
// v5
<Prompt
when={formIsHalfFilledOut}
message="Are you sure you want to leave?"
/>Summary
Replace all <Switch> with <Routes> .
Route props render / component become element , supporting nested routes.
path now supports relative routes and only two dynamic placeholders ( :id and * ).
Case‑sensitive matching can be enabled via caseSensitive .
Trailing slashes are ignored for all path matches.
New Outlet component renders matched child routes.
Redirect is removed; use Navigate instead.
useNavigate replaces useHistory for programmatic navigation.
useRoutes replaces react-router-config for route configuration.
useSearchParams provides a convenient API for query parameters.
Demo code is available on my GitHub blog.
References
React Router documentation
upgrade-to-react-router-v6 guide
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.