React Router DOM (react-router-dom) is the name of the npm package that brought React Router, the most widely used routing library for React, to web browsers. It maps URLs to React components, so a single-page app can have many “pages” with working links, back and forward buttons and shareable URLs, without reloading the page. Today the same features live in the main react-router package, and react-router-dom remains mainly as the import name in existing projects and older tutorials.
This post explains what React Router DOM is and does, how it relates to react-router, the three ways (modes) you can use React Router, and when to choose it or an alternative. It is a conceptual overview: for a hands-on walkthrough, follow my React Router tutorial for beginners, and for what changed between releases, see React Router 8 explained.
What Does React Router DOM Do?
A React app built with Vite (or the old Create React App) is a single HTML page. Without a router, every “page” would need its own state flag, the URL would never change, and refreshing or sharing a link would always land on the home screen. React Router fixes that by keeping the URL and the UI in sync:
- Matches URLs to components.
/users/42renders aUsercomponent with42available as a parameter. - Navigates without reloading. Its
<Link>changes the URL through the browser’s History API and re-renders only what changed, so app state survives. - Supports nested layouts. A parent route renders shared UI (header, sidebar) and an
<Outlet />where child routes appear. - Gives you hooks such as
useParams,useNavigate,useLocationanduseSearchParamsfor reading and changing the URL. - Can load data and handle forms per route (loaders and actions), and render on the server, in its data and framework modes.
The “DOM” in the name refers to the browser’s Document Object Model, the environment it targets (see how the DOM tree works), as opposed to React Native, which once had its own react-router-native package.
react-router vs react-router-dom
This is the most common point of confusion, because the answer depends on the version. I installed each major version and compared what the two packages export:
react-router | react-router-dom | |
|---|---|---|
| v6 | Platform-independent core: Routes, Route, useNavigate, useParams and so on, but no BrowserRouter or Link | Everything in the core plus the browser pieces: BrowserRouter, Link, NavLink, Form, useSearchParams, createBrowserRouter (about 20 extra exports). Web apps installed this one. |
| v7 | Contains everything, including BrowserRouter and Link | A thin re-export of react-router (it depends on it, and Link is literally the same object), kept so existing imports keep working |
| v8 (current) | Contains everything; the DOM-specific RouterProvider and HydratedRouter come from react-router/dom | No v8 release; the latest version on npm is 7.18.4 |
So, in short:
- Starting a new project? Install
react-routerand import from it. There is no reason to addreact-router-dom. - Maintaining an existing project that imports from
react-router-dom? It keeps working on v6 and v7. Moving to v8 means changing those imports toreact-router; the component and hook names are the same. The upgrade details are in my React Router 8 post. - Reading a tutorial that says
react-router-dom? Mentally replace it withreact-router, and check which version it was written for, because v5 code (<Switch>,component={...},useHistory) looks quite different.
The old name is far from dead: in the week of 16 to 22 October 2026, npm counted about 27.5 million downloads of react-router-dom against 35.6 million for react-router, mostly from existing apps and their dependencies.
The Three Modes of React Router
React Router can be used at three levels, each adding features (and responsibility) on top of the previous one. You choose a mode by how you set up the router, not by installing a different package.
1. Declarative mode
Routes are JSX elements inside <BrowserRouter>. It does URL matching, links, navigation and layouts, and leaves data loading to your components (with useEffect or a library such as TanStack Query). This is the classic “React Router DOM” style most people know:
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router';
function Home() {
return <Link to="/users/1">User 1</Link>;
}
function User() {
const { id } = useParams();
return <h1>User {id}</h1>;
}
createRoot(document.getElementById('root')).render(
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<User />} />
</Routes>
</BrowserRouter>,
);2. Data mode
Routes are defined as objects with createBrowserRouter, which lets each route have a loader that fetches data before the page renders, an action for form submissions, and its own error element. React Router then handles pending states and revalidation, which avoids the “render, then fetch, then render again” waterfall:
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, Link, useLoaderData } from 'react-router';
import { RouterProvider } from 'react-router/dom';
const router = createBrowserRouter([
{ path: '/', element: <Link to="/users/1">User 1</Link> },
{
path: '/users/:id',
loader: async ({ params }) => ({ id: params.id, name: `User ${params.id}` }), // runs before render
Component: function User() {
const user = useLoaderData();
return <h1>{user.name}</h1>;
},
},
]);
createRoot(document.getElementById('root')).render(<RouterProvider router={router} />);3. Framework mode
React Router acts as a full-stack framework through its Vite plugin (this part grew out of the Remix framework). Routes live in a config file and route modules, loaders run on the server, pages can be server-rendered or pre-rendered, code is split per route, and types for params and loader data are generated for you:
// app/routes.ts
import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route("users/:id", "routes/user.tsx"),
] satisfies RouteConfig;// app/routes/user.tsx
import type { Route } from "./+types/user";
// Runs on the server (or at build time) before the page renders
export async function loader({ params }: Route.LoaderArgs) {
return { name: `User ${params.id}` }; // params.id is typed from the route path
}
export default function User({ loaderData }: Route.ComponentProps) {
return <h1>{loaderData.name}</h1>;
}I checked all three: the declarative and data examples navigated from / to /users/1 in a headless browser without a page reload and also worked when opening /users/7 directly; the framework example passed type checking and its server returned HTML that already contained <h1>User 42</h1> for /users/42.
| Declarative | Data | Framework | |
|---|---|---|---|
| Set up with | <BrowserRouter> + <Routes> | createBrowserRouter + <RouterProvider> | Vite plugin, routes.ts, route modules |
| Data loading | In components | Route loaders (in the browser) | Route loaders (on the server or at build time) |
| Forms and mutations | Your own handlers | Route actions, <Form> | Route actions, <Form>, server code |
| Server rendering | No | Possible, manual | Built in (SSR, pre-rendering or SPA) |
| Generated route types | No | No | Yes |
| Good for | Simple SPAs, learning, adding routing to an existing app | SPAs with lots of data per page | New full-stack apps, SEO-sensitive sites |
Everything you learn in declarative mode (routes, links, layouts, params, hooks) carries over to the other two, which is why my beginner tutorial starts there.
When to Use React Router, and When to Use Something Else
React Router is the default choice for routing in a React single-page app, but it isn’t the only option. To compare the cost, I built the same two-page app (a home page linking to /users/:id) with each router using Vite 8 and React 19.3 and measured the production JavaScript:
| Router | Bundle (gzipped) | Added on top of React alone |
|---|---|---|
| No router (React only) | 67.7 KB | n/a |
| Wouter 3.13 | 70.1 KB | +2.3 KB |
| React Router 8.5, declarative mode | 80.4 KB | +12.7 KB |
| TanStack Router 1.170 | 93.4 KB | +25.7 KB |
| React Router 8.5, data mode | 97.6 KB | +29.8 KB |
All four routers navigated correctly without reloading the page. Sizes grow as your app uses more features, so treat these as the starting cost, not a ranking of quality.
| Choose | When |
|---|---|
| React Router (declarative or data mode) | You are building a client-rendered SPA with Vite and want the most documented, most widely used option, with a path to server rendering later. |
| React Router (framework mode) | You want a full-stack React framework that stays close to web standards (requests, responses, forms) and can deploy to many hosts. |
| Next.js App Router | You want file-based routing with React Server Components, and a framework with a large ecosystem and hosting options built around it. |
| TanStack Router | You want end-to-end type safety for paths, params and search params, and you already use TanStack Query. |
| Wouter | You need only basic routing in a small app or widget and care about every kilobyte. |
| Expo Router | You are building a React Native app; React Router targets the web. |
| No router | Your app is a single screen, or “pages” are tabs that don’t need their own URLs. |
One thing all client-side routers share: route guards only decide what the browser shows; they don’t protect data. Protected data still needs checks on the server, as I explain in how to create an Auth0 protected route.
Common Misconceptions
- “React Router DOM is part of React.” It isn’t. React only renders components; routing comes from a separate library. React’s own docs recommend starting new apps with a framework, and React Router is one of the ones they list.
- “I need both react-router and react-router-dom.” Not since v7. Installing both versions side by side can even give you two copies of the router.
- “React Router reloads the page.” Only if you use plain
<a href>tags for internal links;<Link>navigates on the client. - “Routing makes my SPA SEO-friendly.” Separate URLs help, but search engines still see a mostly empty HTML page until JavaScript runs. Use framework mode or pre-rendering for pages that need to rank.
- “A refresh on /about returns 404, so the router is broken.” That is the web server looking for a file. SPAs need the host to serve
index.htmlfor unknown paths.
Conclusion
React Router DOM is the browser package of React Router, the library that keeps a React app’s URL and UI in sync. Since v7 the main react-router package contains everything, and in v8 react-router-dom no longer gets releases, so new projects should install and import react-router. Pick declarative mode for a simple SPA, data mode when pages need data before rendering, and framework mode for full-stack and SEO-sensitive apps, or a lighter or more type-focused router such as Wouter or TanStack Router when those trade-offs suit you better. To start building, follow the step-by-step React Router tutorial.
FAQs
What is React Router DOM?
react-router-dom is the npm package that provided React Router for web browsers. It maps URLs to React components so single-page apps can have multiple pages, links and browser history without reloads. Its features now live in the react-router package.
What is the difference between react-router and react-router-dom?
In v6, react-router was the platform-independent core and react-router-dom added browser APIs such as BrowserRouter and Link. In v7, react-router contains everything and react-router-dom only re-exports it. In v8 there is no react-router-dom release; use react-router.
Should I install react-router or react-router-dom?
Install react-router for new projects. Existing projects that import from react-router-dom keep working on v6 and v7, but need their imports changed to react-router to move to v8.
Is react-router-dom deprecated?
It is not formally marked as deprecated on npm, but it has no v8 release; its latest version is 7.18.4. New code should use react-router.
What are the three modes of React Router?
Declarative mode (BrowserRouter and Routes in JSX), data mode (createBrowserRouter with loaders and actions) and framework mode (the Vite plugin with route modules, server rendering and generated types).
Is React Router a framework or a library?
Both. In declarative and data modes it is a routing library inside your app. In framework mode it is a full-stack framework that handles server rendering, data loading and builds.
What are the alternatives to React Router?
Next.js (file-based routing with Server Components), TanStack Router (type-safe routing), Wouter (a very small router) and, for React Native, Expo Router.








