The next major version of React Router, version 6, is just around the corner. We published the first alpha release back in January, and we released another alpha last week.

For version 6, I'm using "alpha" to mean "not yet feature complete". I also anticipate having some minor breaking changes between alphas as we nail down the final APIs. For these reasons, I've been hesitant to publish anything more than a migration guide and a getting started guide for v6 up to this point because I didn't want to publish information about something that we decided to change before the final release.

But we're a bit further along now and I didn't want to cut too many releases before writing up a proper blog post about v6, so here we are. My goal with this post is to give you a few of the highlights from the v6 release and share with you why I'm so excited about it.

This is a pretty long post (lots to talk about!), so here's a quick summary:

Overview

Ryan and I have been working on React Router since May 2014 ... that's a long time! We've been through a lot of iterations and ideas, and we've traveled around the world and talked to literally thousands of React developers over the last few years as part of our React workshops. React Router v6 takes the best of what we've learned over the years, combined with what we know so far about where React is headed in the future, and delivers it in a package that I believe is the best router we've ever built.

Let's check it out.

Smaller Bundles

One really quick thing right off the bat — React Router v6 is a lot smaller than its predecessor. For example, a quick high-level comparison of the bundles for [email protected] vs. [email protected] reveals the total bundle size decreased by 70%. Smaller bundles means your app loads more quickly, especially over slow/poor network connections.

You should also keep in mind that this is just a high-level comparison. React Router contains many modules and hooks that you may not even need in your application, so depending on which pieces you import the numbers will vary.

There are a few reasons for the big change in our bundle size, and I plan on diving into them in a future post. But for now, let's explore some of the API upgrades available in v6.

Introducing <Routes>

One of the most exciting changes in v6 is the powerful new <Routes> element. This is a pretty significant upgrade from v5's <Switch> element with some important new features including relative routing and linking, automatic route ranking, and nested routes and layouts.

Relative <Route path> and <Link to>

Unlike the <Switch> API in v5, all <Route path> and <Link to> values under v6's <Routes> element are automatically relative to the parent route that rendered them. This makes it a lot easier to think about your React Router app as lots of small apps that are just stitched together at different "mount" points.

Pulling an example straight from the v5 to v6 migration guide, a v6 app might look something like this:

import { BrowserRouter , Routes , Route , Link } from 'react-router-dom' ; function App ( ) { return ( < BrowserRouter > < Routes > < Route path = "/" element = { < Home / > } / > < Route path = "users/*" element = { < Users / > } / > < / Routes > < / BrowserRouter > ) ; } function Users ( ) { return ( < div > < nav > < Link to = "me" > My Profile < / Link > < / nav > < Routes > < Route path = "/" element = { < UsersIndex / > } / > < Route path = ":id" element = { < UserProfile / > } / > < Route path = "me" element = { < OwnUserProfile / > } / > < / Routes > < / div > ) ; }

This example probably looks like no big deal if you're learning about React Router for the first time. It all seems very intuitive! But if you're used to how things work in v5, you'll notice we fixed a few things:

You don't need to use an exact prop on <Route path="/"> anymore. This is because all <Route> paths match exactly by default. If you want to match more of the URL because you have child routes (see the <Routes> defined in the Users component above), use a trailing * as in <Route path="users/*"> .

prop on anymore. This is because all paths match exactly by default. If you want to match more of the URL because you have child routes (see the defined in the component above), use a trailing as in . All routes and links automatically build upon the path of the route that rendered them, so e.g. a <Link to="me"> that is rendered inside a <Route path="users/*"> is going to link to /users/me .

If you were doing relative routing and linking in React Router v5, you'll notice you don't need to manually interpolate match.path and match.url anymore.

Nested Routes and Layouts

In a large app it's nice to be able to spread out your route definitions across multiple <Routes> elements so you can do code splitting more easily. But in a smaller app, or with nested components that are closely related, you may want to just see all of your routes in one place. This can help a lot with code readability.

Let's continue with the previous code example, except this time we'll combine all of the routes into a single <Routes> declaration by taking the routes that were defined in our Users component and hoisting them up into the App . We'll also use an <Outlet> in Users where the routes used to be to render the child routes.

import { BrowserRouter , Routes , Route , Link , Outlet } from 'react-router-dom' ; function App ( ) { return ( < BrowserRouter > < Routes > < Route path = "/" element = { < Home / > } / > < Route path = "users" element = { < Users / > } > < Route path = "/" element = { < UsersIndex / > } / > < Route path = ":id" element = { < UserProfile / > } / > < Route path = "me" element = { < OwnUserProfile / > } / > < / Route > < / Routes > < / BrowserRouter > ) ; } function Users ( ) { return ( < div > < nav > < Link to = "me" > My Profile < / Link > < / nav > < Outlet / > < / div > ) ; }

If you compare this example with the previous one, you'll notice a few things:

We're using <Route children> to specify nested routes! The URL paths nest along with the route elements, so /users/me renders <Users><OwnUserProfile /></Users> .

to specify nested routes! The URL paths nest along with the route elements, so renders . We used an <Outlet> element as a placeholder. An <Outlet> in this case is how the Users component renders its child routes. So the <Outlet> will render either a <UserProfile> or <OwnUserProfile> element depending on the current location.

One other small thing you may have noticed is that <Route path="users"> no longer needs the trailing /* on the path. When the routes are all defined together, you don't need it because the router can see all your routes at once.

Note: The ability to nest routes like this was one of our favorite features of React Router v3, but we lost it in v4 when we were more focused on the needs of large apps, like code splitting. Version 6 brings together the best of both worlds. Put all your <Routes> in one spot, or spread them out across your app as needed. It's up to you!

Object-based Routes

Up to this point we've talked about the new <Routes> , <Route> , and <Outlet> APIs which use React elements to declare your routes. I call this API "the JSX API" for routing with React Router.

But React Router v6 ships with another API for routing that uses plan JavaScript objects to declare your routes. In fact, if you look at the source of <Routes> , you'll see that it's really just a tiny wrapper around a hook that is at the heart of the router's matching algorithm: useRoutes .

The useRoutes hook is a first-class API for routing that lets you declare and compose your routes using JavaScript objects instead of React elements. Continuing with the example above, let's see what it looks like with useRoutes .

import { BrowserRouter , Link , Outlet , useRoutes } from 'react-router-dom' ; function App ( ) { let element = useRoutes ( [ { path : '/' , element : < Home / > } , { path : 'users' , element : < Users / > , children : [ { path : '/' , element : < UsersIndex / > } , { path : ':id' , element : < UserProfile / > } , { path : 'me' , element : < OwnUserProfile / > } , ] } ] ) ; return element ; } function Users ( ) { return ( < div > < nav > < Link to = "me" > My Profile < / Link > < / nav > < Outlet / > < / div > ) ; }

The useRoutes hook accepts a (possibly nested) array of JavaScript objects that represent the available routes in your app. Each route has a path , element , and (optionally) children , which is just another array of routes.

The object-based route configuration may look familiar if you were using the react-router-config package in v5. In v6, this configuration format has been promoted to a first-class API in core and the react-router-config package will be deprecated.

Suspense-ready Navigation

Version 6 is a great chance for us to get the router all ready for the future of React: suspense. Instead of giving you access to the history instance directly (usage of which would introduce subtle bugs in a suspense-enabled app), v6 gives you a useNavigate hook. This is useful any time you need to navigate imperatively, e.g. after the user submits a form or clicks on a button.

import React from 'react' ; import { useNavigate } from 'react-router-dom' ; function App ( ) { let navigate = useNavigate ( ) ; let [ error , setError ] = React . useState ( null ) ; async function handleSubmit ( event ) { event . preventDefault ( ) ; let result = await submitForm ( event . target ) ; if ( result . error ) { setError ( result . error ) ; } else { navigate ( 'success' ) ; } } return ( < form onSubmit = { handleSubmit } > < / form > ) ; }

If you need to do a replace instead of a push, use navigate('success', { replace: true }) . If you need state, use navigate('success', { state }) .

In a suspense-enabled app, the navigate function is aware of when your app is suspending. When it is, any push navigation is automatically converted into a replace action. This might be useful when building a single-page app with multiple links, and the user clicks on a link that suspends because it needs to load data, but then quickly clicks on another link and goes to a new page. In this situation the router would automatically omit the first navigation from the browser history because that page never actually loaded.

Of course, the declarative way to navigate around is by putting <Link> s on the page. <Link> uses navigate under the hood to update the URL, so all links will be ready for suspense.

Remaining Features

There is a LOT more in this release than I can cover in a single blog post, but the features I've already mentioned are definitely the highlights. I'll be posting more about some of the other things you can do with v6 very soon.

There are still two more major features I wanna deliver before moving into beta releases: preloading and focus management. We already have some fairly concrete ideas around how these are going to work, and we will have examples soon about how to use both of these features as soon as they are merged into the dev branch where all v6 development is happening.

How to Help

Probably the best thing you can do to help us get v6 across the finish line is to use it and let us know what you think! You can find instructions about how to get started in the Getting Started guide.

If you already have an app on v5, you can follow the guidelines in the migration guide and let us know what snags you run into. Although there are a few breaking changes from v4/5, we did not lose any functionality, only gained some. So everything you were doing before is possible in the new architecture, and quite a few things should be easier!

One of the other major ways you can contribute to the project is through documentation. Good docs are absolutely essential to the success of any open source project. There are a few different ways you can help us with our docs:

Read through the current docs and tell us if something doesn't flow, there's a typo, or something is unclear

Look for TODOs and submit a pull request (PR)

If you're interested in submitting a PR, please read through the contributing guidelines first. They'll help you get all setup.

Hopefully this post has given you some insight into the new features of React Router v6 and what we're hoping to accomplish with it. I hope it has also given you some ideas about how you can get started with it and use it in your app if you'd like.

We have already started building everything that we do on top of v6 and we have received very positive feedback from several early alpha testers. It shouldn't be too long before we get a final release out the door.

Enjoy, and we'll post more updates soon!