Building multi-page React applications requires a robust routing solution that translates URL paths into components on screen. The gold standard for declarative routing in React is React Router DOM.
Over 75% of large React applications and 50% of smaller projects rely on React Router, making it the most popular routing library by far according to the latest State of JavaScript surveys.
This definitive guide will teach you how to master routing in your React apps leveraging the power and flexibility of React Router v6.
Why Routing Matters in React
Let‘s first understand why routing is so essential for crafting dynamic, interactive React applications.
Routing enables you to:
- Break down UIs into modular, reusable components mapped to paths
- Keep UI state encapsulated for each view
- Manage navigation flow and transitions
- Change URL to control browser history
- Render UI responsive to URL changes
- Nest layouts and compose complex UIs
- Integrate data loading lifecycles with location
- And more…
Without routing in place, changes in UI state and user navigation quickly become unmanageable.
Some examples of where routing supercharges your React architecture:
- Multi-step forms or workflows
- Dashboards with data visualization
- Feature areas that load async data
- Public vs private app sections
- Logging users in/out to control access
And as apps grow to 50+ screens, routing is what keeps UIs manageable and flexible.
Now the only question is which routing approach to use…
Battle of React Routers
The React ecosystem has produced many competitive router implementations over the years. Here is how the most popular solutions stack up:
Library | Stars | Size | SSR |
---|---|---|---|
react-router-dom | 35k | 16kB gzipped | Client-only |
Next.js Router | 33k | – | Full SSR |
Reach Router | 4k | 7kB gzipped | Client-only |
Remix Router | 14k | – | Partial SSR |
So why has react-router-dom won out as the routing tool of choice for most React developers?
A few reasons:
✅ Lightweight yet fully-featured
✅ Excellent documentation
✅ Huge community adoption
✅ Flexible and extensible API
It continues to evolve with each major React release. The latest v6 version modernizes React Router while retaining compatibility.
For traditional SPA projects without a backend server component, react-router-dom remains the standard.
Now onto the code!
Installing React Router v6
To get started with React Router in your application:
npm install react-router-dom
Or
yarn add react-router-dom
This will install the package for you to import router components from.
Here are the core imports you‘ll leverage:
import {
BrowserRouter,
Routes,
Route,
Link
} from ‘react-router-dom‘;
This provides:
BrowserRouter
– Enables routing capabilitiesRoutes
– Defines route hierarchyRoute
– Maps path to UI componentLink
– Generates navigation between routes
We‘ll explain how these pieces fit together next.
Router Concepts
At a high level, here is how React Router operates:
When the URL updates in the browser address bar:
- React Router checks the
Routes
configuration you define - Finds the first
Route
path that matches - Displays that route‘s
element
on screen - Updates UI without full page refresh
Under the hood, React Router taps into the browser‘s built-in History API manipulating state and the navigation stack to control transitions between routes.
But enough theory – let‘s jump into some code!
Defining Routes
The starting point for wiring up React router is mapping URL paths to components.
Here is a simple App
component with two route definitions:
import { BrowserRouter, Routes, Route } from ‘react-router-dom‘;
import { Home, About } from ‘./pages‘;
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
Breaking this down:
BrowserRouter
is the root wrapping all routesRoutes
container to define routes- Each
Route
maps a path to display element - Elements can be React components
Now:
- When URL is
/
–<Home>
renders - When URL is
/about
–<About>
renders
This allows you to toggle UI views as the URL updates!
Linking Between Routes
Once you have some routes defined, you need a way for users to navigate between them.
For this, use the <Link>
component:
import { Link } from ‘react-router-dom‘;
function Home() {
return (
<div>
<Link to="/about">About</Link>
</div>
);
}
function About() {
return ;
}
Now when a user clicks the <Link>
, React Router will:
- Change URL to
/about
- Match
<About>
route - Render the
<About>
component 😎
Plus, <Link>
handles many useful features out of the box:
- Accessible focus management
- Prefetching optimizations
- Relative vs absolute paths
- URL generation helpers
This handles navigational heavy lifting so you don‘t have to.
Dynamic Route Parameters
Hardcoding exact path matches works for simple cases. But often you need dynamic, parameterized routes that accept variables.
Say you have a user profile page to render based on an id:
/users/1234
/users/5678
You can define a route parameter like so:
<Route path="/users/:userId" element={<UserProfile />} />
Then inside the UserProfile
component, you can access that userId
value using the useParams
hook:
import { useParams } from ‘react-router-dom‘;
function UserProfile() {
const { userId } = useParams();
return
}
Now as the :userId
portion of the URL changes, UserProfile
will update accordingly!
Composing Layouts with Nested Routes
Complex applications often require multi-level UI nesting.
React Router makes composing layouts simple using nested routes.
Say you have a dashboard with analytics child pages:
+-- /dashboard
|
+-- analytics
|
+-- revenue
|
+-- traffic
|
+-- reports
You can model this with:
function App() {
return (
<Routes>
<Route
path="/dashboard"
element={<DashboardLayout />}
>
<Route path="analytics">
<Route path="revenue" element={<AnalyticsRevenue />} />
<Route path="traffic" element={<AnalyticsTraffic /> />
</Route>
</Route>
</Routes>
);
}
function DashboardLayout() {
return (
<div>
{/* Sidebars/Nav */}
<Outlet />
</div>
)
}
Here is what happens as you navigate:
- When on
/dashboard
– Shows<DashboardLayout>
only - On URL
/dashboard/analytics
– Shows BOTH<DashboardLayout>
and nested<Outlet>
- When on deeper nested route – Shows parent layout + matched nested route element
This pattern is immensely powerful for handling page variability + shared containers.
Authentication and Access Control
Controlling access to protected routes is easy with React Router.
A common pattern is to check auth globally before rendering child route elements:
function ProtectedRoute({ children }) {
const isAuthenticated = checkAuth();
if (!isAuthenticated) {
// If not authenticated - redirect
return <Navigate to="/login" />;
}
// Authenticated - render child routes
return children;
}
function App() {
return (
<Routes>
<Route element={<ProtectedRoute />}>
{/* Protected routes here */}
</Route>
<Route path="/login" element={<Login />} />
</Routes>
)
}
Now any route wrapped via <ProtectedRoute>
requires valid authentication!
Some other authorization patterns:
- Granular access per role
- Mix public/private routes
- Token-based authentication
Proper access control is essential as applications grow.
Code Splitting + Performance
When dealing with many routes, you‘ll want to optimize load performance.
A great way to speed up initial rendering is code splitting – splitting bundle output into separate chunks that load on demand.
Here is how to lazily load routes with React Suspense:
const Dashbaord = React.lazy(() => import(‘./Dashboard‘));
function App() {
return (
<Suspense fallback={<Loader />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
)
}
Now the Dashboard
component bundle splits out into its own JavaScript chunk.
This chunk only loads when user navigates to /dashboard
route resulting in faster first paint!
Recap and Next Steps
Hopefully you now feel empowered to tackle routing in your React applications!
Here are some parting thoughts:
- Experiment with diverse routing patterns
- Split route definitions into logical domain groupings
- Carefully plan navigation UX and access control early on
- Add route animations to delight users
- Monitor bundle sizes and lazy load deferred routes
This is just the beginning of mastering routing. Many additional concepts around query parameters, SSR rendering, custom route matching, and more that build on these foundations.
But absorbing these core principles will guide you far.
For any other questions reach out in the comments!