# Migrating to React Router v6: A Complete Guide

> ***Editor’s note****: This React Router migration guide was last updated on 25 October 2022 to include information about* `useNavigate` vs. `useHistory`, changes to `NavLink`, and more information about React Router v6. 

When we maintain React apps, we’ll need to upgrade dependency libraries by changing our existing codebases over time. For example, upgrading the React Rounder v5 dependency to v6 requires step-by-step changes in your existing codebase. It may be challenging to transition from React Router v5 to v6 because several changes have altered how things were done in React Router v5. In addition, several new features have been introduced, so it is recommended to upgrade to v6 even if the transition is slightly annoying.

To upgrade from React Router v5 to v6, you’ll either need to create a new project or upgrade an existing one using npm. React Router v6 also extensively uses [React Hooks](https://blog.logrocket.com/react-hooks-cheat-sheet-unlock-solutions-to-common-problems-af4caf699e70/), requiring React v16.8 or above. In this article, we’ll look at issues with React Router v5, what changed, how to upgrade to v6, and what benefits this upgrade [offers.To](http://offers.To) follow along, you should be familiar with [React Router](https://blog.logrocket.com/react-router-v6/).

## **Issues with React Router v5**

React Router v5 came close to perfection, but there were still some flaws. The v5 library has some issues with the routing algorithm and routing configuration defaults (JSX component prop defaults). Also, the v5 came with some inconsistent, somewhat complex APIs that affect the developer’s productivity. Moreover, v5’s bundle size weighs more compared to what it offers as a React library. Here is a detailed explanation of the major issues that every developer faces in React Router v5:

### **Path-matching issues**

`Routes` queries route paths with the most similar naming instead of with exact naming. For instance, a route name `/dashboard` would still be called if the browser route is `/dashboard/test`, or `/dashboard/testagain`, etc., which is not expected by app developers. Developers have to explicitly specify the prop `exact` to strictly query route names.

Also, defining routes with the same path but optional parameters requires special arrangements. For instance, the parameter is required when defining the route `/games` and another route with the same name. Thus, developers need to arrange the definition, so the route with the parameter comes first. Otherwise, the routes won’t work as expected because of the issues in v5’s routing algorithm.

Look at the following example that demonstrates the ranking problem in the v5’s routing algorithm:

The correct way: Here, `/games` will render the `Games` component but not `SelectedGame`, because it does not have a parameter `id`. While `/games/:id` will render only the `SelectedGame` component:

```xml
<Router>
  <Route path="/games/:id" component={SelectedGame} />
  <Route path="/games" component={Games} />
</Router>
```

The incorrect way: Here, either `/games` or `/games/:id` will render the `Games` component:

```xml

<Router>
  <Route path="/games" component={Games} />
  <Route path="/games/:id" component={SelectedGame} />
</Router>
```

The above code snippets illustrate the right and wrong way to order routes that are related by paths or parameters according to React Router v5. The first example is the most suitable order, while the second allows only the route `/games` to render for any situation with a parameter.

According to v5’s perspective, the second approach is incorrect, and developers need to use the `exact` prop or re-order (as in the first code snippet) to fix it. However, the v5 route ranking algorithm could be intelligent enough to identify the most suitable route without explicit ordering or `exact` usage.

For developing nested routes, developers had to write more code with the `useRouteMatch` Hook, `Route`, and `Switch` components. For nested routes, developers had to find the entire route because there is no relative route handling in v5:

```xml
// ----
<Switch>
  <Route path="/dashboard" component={Dashboard} />
</Switch>;
// ----

function Dashboard() {
  const { path } = useRouteMatch();
  return (
    <div>
      <h1>Dashboard</h1>
      <Switch>
        <Route path={`${path}/charts`} component={Charts} />
      </Switch>
    </div>
  );
}
```

### **Issues in API consistency and simplicity**

When a specific library offers inconsistent and somewhat complex APIs, application codebases become less maintainable and more error-prone. So, software library developers always try to offer consistent and simple APIs for app developers to manage their codebases better. However, library developers can’t offer the most optimal API design for app developers with their first release. API improvements typically come with the open-source developer community’s feedback, bug reports, and feature requests.

React Router v5 has some inconsistent APIs and unfamiliar naming. Also, app developers had to write more [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code) than they expected to configure and integrate React Router v5 into their apps. For example, for programmatic navigation, v5 offers the `useHistory` Hook, but almost all React Router newcomers expect `useNavigate`.

Also, the `Route` component offers two different props to link components: `component` and `render`, but it’s possible to offer one `element` prop as the [Suspense API](https://blog.logrocket.com/react-suspense-data-fetching/#what-is-react-suspense) does. Moreover, for defining your routes with [JavaScript objects](https://blog.logrocket.com/tag/vanilla-javascript/) in v5, you have to use the separate [`react-router-config`](https://www.npmjs.com/package/react-router-config) package.

### **Excessive bundle and code size**

As we already know, when we use heavy dependencies, our applications also become heavy. Similarly, if we use a library that requires writing verbose API calls, our application bundles also contain excessive code, which leads to heavy bundle sizes.

React Router v5 latest stable release ([v5.3.4](https://bundlephobia.com/package/react-router-dom@5.3.4)) ‘s bundle size is about four times larger than [React](https://bundlephobia.com/package/react@18.2.0)!Having considered the issues with React Router v5, we will now discuss how to migrate and what has changed that makes React Router v6 different.

## **Migrating to React Router v6**

The following sections will teach you how to upgrade to React Router v6 in projects where React Router is already installed and from scratch.

### **Upgrading React Router in a project where it is already installed**

To upgrade the React Router version, open a terminal and navigate to the project directory where you wish to upgrade it.

To see a list of the project’s dependencies, use the following command:

```bash
npm ls
# --- or ---
yarn list
```

You should see a list like this:

![](https://paper-attachments.dropboxusercontent.com/s_54146A2F584E7952F77DC32D411741D277C15DC44BE60F3EBF781B7EBBD7AABC_1638888973788_image.png align="left")

Although the list generated may not be exactly the one above, you should see an entry with its installed version if you have React Router installed.

Next, run the following command to initiate an upgrade:

```bash
npm install react-router-dom@latest
# --- or ---
yarn install react-router-dom@latest
```

If you execute this command without being connected to the internet, it will fail because some files must be downloaded during the installation. If everything is in order, you should see something similar; in our case, the version is v6.0.2:

![](https://paper-attachments.dropboxusercontent.com/s_54146A2F584E7952F77DC32D411741D277C15DC44BE60F3EBF781B7EBBD7AABC_1638994774561_image.png align="left")

If everything goes well, we should notice that React Router has been updated when we run the `npm ls` or `yarn list` command again:

![](https://paper-attachments.dropboxusercontent.com/s_54146A2F584E7952F77DC32D411741D277C15DC44BE60F3EBF781B7EBBD7AABC_1638994912612_image.png align="left")

### **Installing React Router from scratch**

First, open a terminal in a project directory where React Router isn’t installed.

To install a specific version of React Router, run the following:

```bash
npm install react-router-dom@[VERSION_TO_BE_INSTALLED]
```

Replace `[VERSION_TO_BE_INSTALLED]` with the version you want to install, for example, `6.0.2`.

Or, run the following code to install the newest version:

```xml
npm install react-router-dom
# --- or ---
yarn install react-router-dom
```

This installation also demands the use of the internet. If the installation went well, you should see something similar to this:

![](https://paper-attachments.dropboxusercontent.com/s_54146A2F584E7952F77DC32D411741D277C15DC44BE60F3EBF781B7EBBD7AABC_1638996731135_image.png align="left")

## **What’s changed in React Router v6?**

It’s important to understand what changed so we can see why upgrading to React Router v6 is helpful. Note that in certain circumstances, developers will downgrade a program to increase functionality or avoid issues.

We will go through the changes in React Router v5 that one should consider when choosing which version to implement in a project.

### **Setting up routes**

We had three different techniques for generating routes in React Router v5, which caused confusion. The first technique is to pass the `component` and `path` as props of the `Route` component:

```xml
<Route path="/games" component={Games} />
```

This works well, however, we cannot pass props to the rendered component. The second is to pass in the component as a child of the `Route` component:

```xml
<Route path="/games">
  <Games count=”10” category=”Action” />
</Route>
```

We may pass custom props to the component we want to render using this approach. The third and final technique is to use the `render` prop where a function returns the component to be rendered:

```xml
<Route path="/games" render={(props) => <Games {…props} />} />
```

This also works and lets us give props to the component we’re rendering. However, it is ambiguous and prone to inaccuracy.

In React Router v6, routes have been simplified to the point that we no longer need to utilize `Switch` to query them. Instead, we utilize a “required” component called `Routes`, which only searches for routes by name. The `*` character can be used to query using a wildcard.

We then supply the component to be rendered to the `Route` component as `element` props. We can also supply custom props to the components in each route we wish to render.

The code snippets below demonstrate how to define routes in v6:

```xml
<Routes>
  <Route path="/games" element={<Games />} />
  <Route path="/movies" element={<Movies genre="Action" age="13" />} />
</Routes> 
```

You can also use `useRoutes` to query the routes in the app. To do this, we must alter the `index.js` content, where we change the `App` wrapper from `React.StrictMode` to `BrowserRouter` as below:

```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom'

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);
```

Then, in the `App.js` file, we define the routes by performing the following:

```javascript
import { Outlet, useRoutes } from 'react-router-dom';
const App = () => {
  const routes = useRoutes([
    {
      path: '/',
      element: <div>Hello Index</div>
    },
    {
      path: 'games',
      element: <Games />,
      children: [
      {
        path: '',
        element: <div>Games Index</div>
      },
      {
        path: ':id',
        element: <div>Game Details</div>
      }]
    }
  ]);
  return routes;
}

const Games = () => {
  return (
    <div className="Games">
      <div>This is the Games page</div>
      <Outlet />
    </div>
  );
}

export default App;
```

Here, we imported `Outlet` and `useRoutes`. The `useRoutes` allows us to define the routes as an array of objects in which we can specify a `path`, the `element` to be rendered when the path is browsed, and sub-paths. `Outlet` helps us render the child route that matches the current path.

## `useNavigate` vs. `useHistory`

In React Router v5, we use `useHistory()` for handling navigation programmatically. There have been concerns with this technique, such as naming confusion and having two methods for navigation, `history.push` and `history.replace`.

To implement navigation in v5, we usually do the following:

```javascript
import { useHistory } from 'react-route-dom';

const App = () => {
  const history = useHistory();
  const handleClick = () => {
    history.push('/home');
  }
  return (
    <div>
      <button onClick={handleClick}>Go Home</button>
    </div>
  ) 
}
export default App;
```

We use the history if we need to replace the current history `frame.replace('/home')` method call.

In v6, we use `useNavigate` instead of `useHistory`:

```javascript
import { useNavigate } from "react-router-dom";

const App = () => {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate("/home");
  }
  return (
    <div>
      <button onClick={handleClick}>Go Home</button>
    </div>
  );
}
export default App
```

Instead of `history.replace`, we can call the navigate method with an options object as follows:

```javascript
const handleClick = () => {
  navigate('/home', { replace: true });
}
```

So, what happened to the `go`, `goBack`, and `goForward` methods in the legacy history API in v5? In v6, we can call the same navigate function with numbers. For example, look at the following code snippet that demonstrates `go back`, `forward`, and `2nd history` frame navigation:

```javascript
navigate(-1)   // v5's history.go(-1) or history.goBack()
navigate(1)    // v5's history.go(1) or history.goForward()
navigate(2)    // v5's history.go(2)
```

Also, v6 replaces `Redirect` with a declarative version of `useNavigate`, `Navigate`:

```javascript
import { Navigate } from 'react-router-dom';

function App() {
  return <Navigate to='/home' replace state={state} />;
}
```

Moreover, the navigate API is now suspense-ready. You can inspect a sample suspense-based router implementation from [this source file on GitHub](https://github.com/remix-run/react-router/blob/4d915e3305df5b01f51abdeb1c01bf442453522e/examples/data-router/src/routes.tsx#L289).

## **Understanding changes in** `NavLink`

The v6 versions also changed the `NavLink` component interface that helps you create breadcrumbs, tabs, and navigation menus with dynamic styles. This is a frequently used component in most React Router-based apps, so you may have to allocate more time for `NavLink` related rewrites.

### **Specifying exact route paths in** `NavLink`

In v5, we use the prop when enforcing an exact path or route. To implement this, we do the following:

```xml
<NavLink to="/dashboard" exact>Go Home</NavLink>
```

In v6, we use the `end` prop to ensure exact routes:

```xml
<NavLink to="/dashboard" end>Go Home</NavLink>
```

The React Router team renamed this prop to align the library with the React ecosystem’s practices.

### **Styling an active** `NavLink`

In React Router v5, we use the `activeClassName` or `activeStyle` props to style a `NavLink` that is currently active.

For instance:

```xml
<NavLink 
  to="/home" 
  style={{color: 'black'}} 
  activeStyle={{color: 'blue'}} 
  className="nav_link" 
  activeClassName="active" >
  Go Home
</NavLink>
```

In v6, we have to use a function with the [destructured](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring) argument `{isActive}` to condition the `style` or `className` to be used for an active `NavLink`.

For instance:

```xml
<NavLink 
  to="/home" 
  style={({isActive}) => ({color: isActive ? 'blue' : 'black'})} 
  className={({isActive}) => `ln-${isActive ? ' active' : ''}`} >
  Go Home
</NavLink>
```

These changes also fix the consistency and simplicity issues in the v5 legacy API. However, if your React app uses the above-deprecated props extensively, you can make your migration process faster and smoother by creating a temporary wrapper for v6’s `NavLink` as follows:

```javascript
import * as React from 'react';
import { NavLink as BaseNavLink } from 'react-router-dom';

const NavLink = React.forwardRef(
  ({ activeClassName, activeStyle, ...props }, ref) => {
    return (
      <BaseNavLink
        ref={ref}
        {...props}
        className={({ isActive }) =>
          [
            props.className,
            isActive ? activeClassName : null,
          ]
            .filter(Boolean)
            .join(" ")
        }
        style={({ isActive }) => ({
          ...props.style,
          ...(isActive ? activeStyle : null),
        })}
      />
    );
  }
);
```

The above code converts the v6 `NavLink` to a v5-like `NavLink` and lets you deploy the app with v6 by keeping working v5-styled `NavLinks` that you can re-write without a rush.

### **Use** `useMatch` instead of `useRouteMatch`

In v5, `useRouteMatch` was used to create relative sub-route paths that matched a particular route. We can use this Hook with or without the pattern argument as follows:

```javascript
// Receive the matched path and url of the current <Route/>
const { path, url } = useRouteMatch();

// Receive the matched route details based on the pattern argument
const match = useRouteMatch('/users/:id');
```

In v6, we use `useMatch` for this. Using the `useMatch` Hook requires a pattern argument and does not accept patterns as an array. So, the following code snippet is valid for v6:

```javascript
const match = useRouteMatch('/users/:id');
```

However, v6 throws an `error` if you don’t pass the pattern argument to the Hook. The above `match` constant will receive [details about the route match](https://github.com/remix-run/react-router/blob/main/docs/utils/match-path.md).

### **Relative routes support for nesting**

In v5, creating JSX-based nested routes generated more boilerplate code segments since we had to construct full URLs explicitly. Consider the following example code snippet:

```javascript
// ----
<Switch>
  <Route path="/dashboard" component={Dashboard} />
</Switch>;
// ----

function Dashboard() {
  const { path } = useRouteMatch();
  return (
    <div>
      <h1>Dashboard</h1>
      <Switch>
        <Route path={`${path}`} component={Summary} />
        <Route path={`${path}/stats`} component={Stats} />
        <Route path={`${path}/charts`} component={Charts} />
      </Switch>
    </div>
  );
}
```

Here we need to manually construct the full path for each’s `path` prop using the `useRouteMatch` Hook. So, when you develop complex apps, you can see this boilerplate code everywhere.

React Router v6 made JSX-based nested routes definitions so minimal. Look at the following v6 re-write of the above v5 sample code snippet:

```javascript
// ---
<Routes>
  <Route path="/dashboard" element={<Dashboard/>}>
    <Route path="" element={<Summary/>}/>
    <Route path="stats" element={<Stats/>}/>
    <Route path="charts" element={<Charts/>}/>
  </Route>
</Routes>
// ---

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Outlet/>
    </div>
  );
}
```

Here v6 lets developers use the `Route` component in a nested way to define nested routes. Note the `Outlet` component that renders nested components: `Summary`, `Stats`, and `Charts`. Here we don’t need to prepend `/dashboard/` to all nested routes, unlike v5, since v6 supports relational path definitions.

## **What was removed from React Router v6?**

In React Router v6, some features are no longer supported because they are ambiguous or faulty. So far, two features were found to have been removed:

### `Prompt`, `usePrompt`, and `useBlocker`

* `usePrompt` (JSX `Prompt`): Was used to confirm whether a user wants to exit a page when the page is in a state that does not require the user to leave
    
* `useBlocker`: Similar to `usePrompt`, and was used to prevent users from leaving a particular page
    

These two features are similar and have the same issue. In a situation where a user tries to navigate outside a page and navigation is restricted by either `usePrompt` or `useBlocker`, the URL path changes even though the navigation is prevented.

Although there were issues raised about `useBlocker` and `usePrompt`, the creators are still considering adding them back to v6 after they release a stable version. [Check here for more details](https://github.com/remix-run/react-router/issues/8139#issuecomment-954427837). Meanwhile, the developer community created libraries like [`react-router-prompt`](https://www.npmjs.com/package/react-router-prompt) for app developers who need these removed features in React Router v6.

### **Nested routers support**

Using one `BrowserRouter` component is enough for developing advanced React app navigations. Some app developers who used React Router v5 practiced using nested routers in their apps to isolate complex components. For example, most developers used v5’s `MemoryRouter` inside the `BrowserRouter` instance to insolate modals and tab groups. Unfortunately, v6 dropped supporting nested routers and motivated app developers to use only one preferred router implementation.

Most app developers who built nested routers now request the deprecated feature back in v6 to upgrade their v5 React Router dependency to v6. Some developers worry about this breaking change in v6 and even [consider migrating](https://github.com/shapeshift/web/pull/968#issuecomment-1033431536) to another React routing module.

According to [this discussion](https://github.com/remix-run/react-router/issues/7375), it seems that maintainers are not focusing on nested router support during the current development timeline. But, the developer community may support maintainers to at this deprecated feature soon! Or, maintainers will offer a production-friendly workaround for this issue.

The developer community found the following workaround (rather a hack) to enable nested routers in v6, but it’s not production-friendly due to unexpected behaviors:

```javascript
<UNSAFE_LocationContext.Provider value={ null }>
  <MemoryRouter>
    { /* some routes not related to the outer router */ }
  </MemoryRouter>
</UNSAFE_LocationContext.Provider>  
```

Subscribe to issue [#7375](https://github.com/remix-run/react-router/issues/7375) on GitHub and get updates related to the v6 nested routers.

## **Why doesn’t React Router v6 work in your app?**

You might notice unexpected behaviors, error messages, and warning messages once you complete the migration process. You may have to solve a bug due to a breaking change in v6. We can’t ship apps to production with routing issues. So, detecting routing issues with a string test plan is better.

Your React Router integration won’t work correctly after upgrading to v6 because of the following issues.

### **The** `history.push` method won’t work properly

You’ve probably updated the `history` package to the latest without updating React Router to v6. The v5 `history` package is compatible with React Router v6, not React Router v5. To solve this, first, update React Router, then it will automatically download a compatible `history` version. If you still need to run React Router v5, use `history` v4 (i.e., v4.10.1). See [this issue](https://github.com/remix-run/history/issues/804).

### **Export** `x` was not found in `react-router-dom`

You are importing a Hook or component that was removed or renamed in v6. For example, `useRouteMatch` won’t work in v6. To solve this, make sure that you don’t import deprecated v5 Hooks or components.

### **Nested routes won’t render components**

Make sure to add `<Outlet/>` in the parent component that embeds nested route components. Check the previous `Dashboard` component as a reference implementation.

### `x` is not a `<Route>` component

In v6, `Routes` can contain only `Route` or `React.Fragment` children, so make sure to move other HTML elements and components to suitable places from the `Routes` component.

### `meta.relativePath.startsWith` is not a function

You are using an object in the `path` prop of `Route`. In v6, you can’t use Regex objects or arrays as a path. Make sure that you use only strings for paths.

Here we discussed some frequently occurring issues. But, you might get a unique error message you can’t find on the internet. Inspect your codebase thoroughly for deprecated API usage in such scenarios, then open a new discussion thread in [React Router.](https://github.com/remix-run/react-router/discussions)

## **Benefits of React Router v6 over v5**

It’s pointless to migrate if one version doesn’t offer any advantages over the other. In this section, we’ll talk about the benefits of upgrading from React Router v5 to v6.

One of the most important considerations for developers is the application’s portability. The size of React Router v6 has been reduced significantly compared to previous versions. The difference between v5 and v6 is around 60 percent.

Here is a comparison from [Bundlephobia](https://bundlephobia.com/):

![](https://paper-attachments.dropboxusercontent.com/s_54146A2F584E7952F77DC32D411741D277C15DC44BE60F3EBF781B7EBBD7AABC_1639015877696_image.png align="left")

![](https://paper-attachments.dropboxusercontent.com/s_54146A2F584E7952F77DC32D411741D277C15DC44BE60F3EBF781B7EBBD7AABC_1639015714297_image.png align="left")

Compiling and deploying apps will be easier and faster due to the size reduction. In addition, the bulk of the modifications in React Router v6 that we covered are more beneficial and have fewer issues.

As previously mentioned, React Router v6 allows routes to accept components as elements when constructing routes, and pass custom props to components. We may also nest routes and use relative links as well. This helps us prevent or reduce the definition of new routes inside various component files and makes it easy to pass global data to components and props.

When redirecting instead of using `useHistory`, the new approach using `useNavigate` has been designed to offer better navigation API. It allows us to set the new route easily via `navigate()` instead of using multiple methods: `history.push()`, `history.replace()`, and `history.goBack()`. Also, the new v6 API is so minimal and developer friendly. It lets you reduce boilerplate code and complex route definitions to make your app codebase clean.

Finally, the navigation link `NavLink` allows us to condition active links, which makes our code neater and simpler, instead of passing two different props for an active and inactive state. By doing this, we can easily use a [ternary operator](https://blog.logrocket.com/a-comprehensive-guide-to-javascript-expressions/) to condition which style will be affected when a link is active or not.

## **Conclusion**

After reading this article, I hope you are able to upgrade your React Router version and restructure your codebases to work seamlessly with React Router v6. Updating your code based on the above points helps you to migrate any React Router-based app. But, if your app’s routing is so complex and extensively uses deprecated v5 features, you can consider using the [official incremental migration guide](https://github.com/remix-run/react-router/discussions/8753) from maintainers.

Not only should React Router v5 be upgraded to v6, but Reach Router should be switched to v6 as well.

Regularly upgrade React Router v6 and review the [documentation](https://reactrouter.com/en/main) for any changes to stay up-to-date with new releases.
