See Our Public Workshops:
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:
- Smaller Bundles
- Introducing
<Routes>
- Object-based Routes
- Suspense-ready Navigation
- Remaining Features
- How to Help
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
react-router-dom@5.1.2
vs.
react-router-dom@6.0.0-alpha.2
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() {/* All <Route path> and <Link to> values in thiscomponent will automatically be "mounted" at the/users URL prefix since the <Users> element is onlyever rendered when the URL matches /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 theUsers
component above), use a trailing*
as in<Route path="users/*">
. - 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>
. - We used an
<Outlet>
element as a placeholder. An<Outlet>
in this case is how theUsers
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 plain 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() {// We removed the <BrowserRouter> element from App because the// useRoutes hook needs to be in the context of a <BrowserRouter>// element. This is a common pattern with React Router apps that// are rendered in different environments. To render an <App>,// you'll need to wrap it in your own <BrowserRouter> element.let element = useRoutes([// A route object has the same properties as a <Route>// element. The `children` is just an array of child routes.{ 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!
Instead of having comments here in our blog, we've tweeted about this post so you can comment there if you wish.
View on Twitter