Frontend Development 13 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Migrating from React Router v5 to v6: Key Differences and New Features

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 App

New 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

frontendMigrationRoutingReact Routerv6v5
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.