When I first started working with React for building single-page applications (SPAs), one of the essential libraries that kept popping up in every tutorial and documentation was React Router. And every time I installed it using npm, I found myself typing:
npm install react-router-dom
But then I started wondering: if React Router is the actual routing library, why are we installing react-router-dom
instead? And even more confusing — there’s also something called react-router
separately. So what exactly is the difference between the two? Is react-router-dom
just a fancy wrapper? Why do we need both?
If you’ve had the same questions or even used react-router-dom
without thinking too much about what react-router
does behind the scenes, don’t worry — you’re not alone. It took me some digging, experimentation, and reading official docs (and GitHub issues) to get a solid understanding of what’s really going on. So let’s break it all down.
Understanding the Basics
Let’s start with the basics. At its core, React Router is a routing library built for React. It allows you to:
- Navigate between different components/pages
- Handle dynamic routing based on URL
- Protect routes (e.g., private routes requiring login)
- Handle nested routing
- Pass data through routes (via params, query, or state)
React Router is one of the most commonly used libraries for client-side routing in React apps.
But here’s where things start to split: React Router is built in a platform-agnostic way.
That means the core react-router
library doesn’t care whether you are using it in a browser (like Chrome), in React Native (for mobile apps), or even in an Electron app. It just provides the core routing logic. The actual implementation — like how to manipulate the browser’s history or how to render links — is delegated to platform-specific packages.
And this is where react-router-dom
and react-router-native
come in.
React Router: The Core Package
The react-router
package is the heart of the routing logic. It includes all the core APIs, like:
createBrowserRouter
createRoutesFromElements
RouteObject
- Navigation logic
- Matchers
- Loaders
- Actions
But it does not include any browser-specific implementation. That means, if you try to use it on its own in a browser app, you won’t have things like:
<BrowserRouter>
<Link>
<NavLink>
Because those are all browser-specific — they rely on the DOM and the browser’s history API.
To put it simply:
react-router
is like the engine of a car. It powers everything but can’t run on its own unless you install it in the right body — like a browser or a mobile shell.
React Router-DOM: The Browser Shell
react-router-dom
is the package that glues the core react-router
logic with the browser’s capabilities. It includes everything from react-router
, plus DOM-specific components like:
<BrowserRouter>
<HashRouter>
<Link>
<NavLink>
<Prompt>
(in v5)useNavigate
useLocation
useParams
These components and hooks know how to interact with the browser’s window.location
, the history stack, and how to render actual clickable <a>
tags that users interact with.
If you inspect the source code or the dependencies of react-router-dom
, you’ll notice it imports a lot from the react-router
core. That’s because react-router-dom
is essentially a higher layer built on top of the core routing engine.
A Real-World Analogy
Imagine react-router
as a generic navigation system — like Google Maps APIs. It knows how to calculate routes, give turn-by-turn directions, and estimate time — but it doesn’t care if you’re driving a car, biking, or walking.
Now, react-router-dom
is like a car dashboard with Google Maps installed. It takes the routing engine and connects it to real-world controls — steering, acceleration, display.
Similarly, react-router-native
is like the same Google Maps API but integrated into a mobile app — maybe with gestures, touch input, and native transitions.
Installation: Why Do We Install react-router-dom?
In most cases, especially when building web apps, you don’t install react-router
manually. You just install react-router-dom
, which includes everything you need — including the core.
npm install react-router-dom
Under the hood, this package already includes the correct version of react-router
, so you don’t need to add both.
However, if you’re building a cross-platform routing library or writing something very custom, you might directly depend on react-router
to access core APIs only.
Versioning Note: React Router v6+
Before React Router v6, react-router
and react-router-dom
were more loosely coupled, and their version numbers sometimes differed slightly. But starting from v6, the React Router team has unified the API more cleanly and made sure that react-router-dom
is just an extended layer over the shared react-router
core.
This makes it easier to understand and upgrade because:
react-router
andreact-router-dom
will typically be on the same version- Most of the logic is shared
- Documentation is centralized
Let’s Look at Some Code
Let me show you a simple example that highlights where the difference lies.
Example 1: Core Logic (from react-router
)
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter([
{
path: "/",
element: <HomePage />,
children: [
{
path: "about",
element: <AboutPage />,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
Here we are using the Data Router APIs introduced in v6.4+, which are part of the core react-router
, but surfaced through react-router-dom
for browser use.
Example 2: Traditional Browser Router (DOM-specific)
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</BrowserRouter>
);
}
Notice how BrowserRouter
is tied directly to the DOM — it uses the HTML5 History API under the hood.
When Should You Use Each?
Use react-router-dom
When:
- You’re building a React app for the browser
- You want DOM-based components like
<Link>
,<BrowserRouter>
, etc. - You want access to the complete routing experience out of the box
Use react-router
Only When:
- You’re building a platform-specific routing solution (e.g., for React Native, Electron, or custom environments)
- You’re writing a custom wrapper or library that builds on routing logic
- You want to tightly control how navigation is handled (not typical for most developers)
Common Misconceptions Cleared
Here are a few things I misunderstood at first — maybe you have too.
❌ Misconception 1: You only need one or the other.
✅ Truth: You typically only install react-router-dom
, but it internally uses react-router
. Think of them as parts of the same system — the core and the interface.
❌ Misconception 2: They are interchangeable.
✅ Truth: No, they serve different purposes. react-router-dom
is for web apps. react-router-native
is for mobile. react-router
is the shared foundation.
❌ Misconception 3: You need to install both manually.
✅ Truth: Installing react-router-dom
is enough for web apps. It includes everything from the core.
Conclusion
So what’s the real difference between react-router
and react-router-dom
?
react-router
: platform-independent routing core.react-router-dom
: DOM-specific implementation that enables React Router to work in the browser.
In my journey of learning React, I found that truly understanding the ecosystem often means looking past the surface. At first, everything might feel abstract or redundant — but when you understand the architecture behind it, it becomes crystal clear.
If you’re working on a browser-based React project (which most of us are), just stick with react-router-dom
. But if you’re exploring advanced use cases or want to know how the tools you’re using are structured, digging into the distinction between these packages is a great place to start.
Hope this clears it up for you like it did for me!