Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To migrate a React Router v5 application to v6, update route declarations from Switch and component to Routes and element, replace history and route-prop access with hooks, and review nested routes and links for v6’s matching rules. For a large application, the official React Router migration guide recommends using react-router-dom-v5-compat to move one route branch at a time; smaller applications may be easier to convert in one release.

Choose a migration approach

Convert the application in one release

A direct conversion can be simpler for a small application or a team able to test and release the whole route tree together. It avoids maintaining a temporary compatibility layer, but makes the route and component changes part of one larger migration.

Migrate incrementally with the compatibility package

For a large application or one that needs frequent releases, the official guide recommends react-router-dom-v5-compat. It allows v5 and v6 APIs to run together so a team can migrate one route subtree at a time and continue shipping between changes. The temporary setup places CompatRouter immediately inside the existing v5 BrowserRouter; a branch being migrated uses CompatRoute.

This approach adds temporary dependency and route-tree complexity. It is most useful when reducing the size and release risk of each change matters more than keeping the migration setup simple. The guide presents this as an incremental migration path, not a permanent mixed-version architecture.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check prerequisites and find v5 patterns

The React Router migration guide requires React 16.8 or newer because v6 uses Hooks. Before changing routes, inventory the places where the application depends on v5 APIs and route-context values. This gives you a checklist for the conversion and helps identify branches that will need more than a syntax change.

  • Switch, Route, Redirect, and route declarations using component or render children
  • useHistory, withRouter, and direct use of props.match or props.location
  • match.path and match.url, especially when constructing child routes or links by string interpolation
  • exact, activeClassName, and activeStyle

Mark which routes render descendant routes, use redirects or guards, or rely on query-string changes. Those cases deserve explicit testing after conversion.

Map the v5 APIs to v6

Use this mapping as a conversion checklist. The most important differences are not just renamed components: v6 route matching, nested paths, navigation, and active-link styling work differently.

v5 pattern v6 pattern Migration note
Switch Routes v6 ranks candidate matches rather than selecting by declaration order.
component={Home} element={<Home />} Supply the rendered route element as JSX.
exact Usually remove it Review nesting and descendant-route behavior instead of carrying over v5 matching assumptions.
props.match.params useParams() Hooks are used in function components; class components that need this value need an appropriate conversion or wrapper.
props.location useLocation() Read the current location from router context.
history.push(path) navigate(path) Call the function returned by useNavigate().
history.replace(path) navigate(path, { replace: true }) Replaces the current history entry.
history.go(-1) navigate(-1) Numeric deltas move through the history stack; use one only when an entry is expected.
String interpolation with match.url Relative to value Route-relative links can avoid manually concatenating path segments.
NavLink exact NavLink end Active class and style values use callbacks in v6.

Convert navigation and route-context access

Replace route props and the v5 history object with the hooks that expose the same kinds of information in v6. For example, a function component can read a route parameter and navigate after an action like this:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { useNavigate, useParams } from "react-router-dom";

function UserPage() {
  const { userId } = useParams();
  const navigate = useNavigate();

  function openAnotherPage() {
    navigate("/elsewhere");
  }

  function returnToPreviousPage() {
    navigate(-1);
  }

  return (
    <button onClick={openAnotherPage}>Open page for {userId}</button>
  );
}

Use useLocation() where a component previously read props.location. For a replacement navigation, pass { replace: true } in the options object to navigate. If a class component reads props.match.params, it cannot call a Hook directly; convert it to a function component or provide the needed value through a suitable component boundary.

Rewrite route declarations and nested routes

Replace each v5 Switch with Routes, then express the rendered component through the route’s element prop. Remove exact after deliberately reviewing the route’s intended shape.

<Routes>
  <Route path="/home" element={<Home />} />
  <Route path="/users/:userId" element={<UserPage />} />
</Routes>

V6 chooses the best matching route instead of relying on the order in which children appear in Switch. That reduces ordering-related unreachable-route problems, but it does not remove the need to design route nesting carefully.

When a route renders another Routes tree

If a parent route renders descendant Routes, add a trailing /* to the parent path so it can match the descendant URL. Convert child paths that were built from match.path into relative paths within the nested route tree.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<Routes>
  <Route path="/account/*" element={<Account />} />
</Routes>

function Account() {
  return (
    <Routes>
      <Route path="settings" element={<Settings />} />
    </Routes>
  );
}

Review any splat path, meaning a path ending in *, and verify that its descendants resolve as intended. A parent without the trailing splat may not match a deeper URL handled by its child route tree.

Update links and active navigation

Replace links assembled from match.url with relative to values where the link should follow the current route context. Route-relative resolution is the default. When path-relative behavior is the intended behavior, set relative="path".

For navigation links, replace exact with end when active state should require the link’s path to end at that route. Convert activeClassName and activeStyle into callbacks that inspect the active state:

<NavLink
  to="settings"
  end
  className={({ isActive }) => isActive ? "selected" : undefined}
  style={({ isActive }) => ({ fontWeight: isActive ? "bold" : "normal" })}
>
  Settings
</NavLink>

Check links at more than one nesting level. A relative destination is resolved against router context, so replacing string concatenation without checking the current route can change where a link leads.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Run the migration in reviewable slices

  1. Check React. Upgrade to React 16.8 or newer if the application is below the migration guide’s prerequisite.
  2. Inventory v5 usage. Search for the route components, history APIs, route props, matching flags, and active-link props listed above. Note nested route trees and behavior that needs verification.
  3. Set up compatibility mode if migrating incrementally. Install react-router-dom-v5-compat and render CompatRouter immediately inside the existing v5 BrowserRouter.
  4. Start at a leaf route. Change that route to CompatRoute, then migrate its component tree from route props and history access to useParams, useLocation, and useNavigate. Commit each coherent slice.
  5. Modernize that branch’s links. Replace manual match.url interpolation with relative link targets where appropriate; convert NavLink exact and its active class or style props.
  6. Convert a complete branch to v6 route declarations. Change its Switch to Routes and its route declarations to element props. Add a trailing /* to a parent route that renders descendant Routes, and make the descendants’ paths relative as needed.
  7. Repeat upward. Continue through the ancestor route trees, checking how each parent and child path composes.
  8. Remove compatibility mode when every branch uses v6 APIs. Uninstall react-router-dom-v5-compat, remove obsolete direct history or react-router dependencies as applicable, install react-router-dom@6, remove CompatRouter, and replace compatibility imports.

Test behavior that syntax changes cannot prove

A successful build does not establish that a route tree behaves correctly. Exercise the application’s own test and staging environments, including the following paths:

  • Open each route directly, including deep links, and refresh the page there.
  • Follow internal links from both parent and nested routes; verify the destination at each nesting level.
  • Test redirects, guarded routes, and the not-found route.
  • Navigate backward and forward, including any code that calls navigate(-1).
  • Render nested route content and confirm parent paths with descendant Routes include the needed splat.
  • Change query strings and confirm components that depend on location respond as expected.
  • Check active navigation styling at the exact route and at deeper URLs to verify the chosen end behavior.

These checks are application-specific; no test results are implied by the migration steps above.

Common migration mistakes

  • Changing only component names. Switching to Routes without converting route rendering to element leaves a v5 declaration pattern behind.
  • Keeping manually concatenated URLs. Old match.url construction can produce incorrect paths after nesting changes; prefer an intentional relative target.
  • Forgetting the parent splat. A route that renders descendant Routes needs a trailing /* to match deeper paths.
  • Treating route order as the matching strategy. V6 ranks matches, so review specificity and nesting rather than relying on child declaration order.
  • Replacing every back action with an assumed history entry. navigate(-1) moves through the existing history stack; it is not a guaranteed destination when no prior entry is available.
  • Porting active-link props unchanged. Use end and callback-based className or style in place of the v5 props.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.