react-router provides the core routing logic and environment-agnostic components for React applications, serving as the foundation for routing behavior. react-router-dom builds on top of react-router by adding specific bindings for web browsers, such as HTML5 history integration and clickable link components. While react-router handles the universal state and matching logic, react-router-dom implements the actual interaction with the browser's address bar and navigation events. Most web developers will install react-router-dom, which automatically includes react-router as a dependency, but understanding the distinction is vital for testing and cross-platform sharing.
When working with routing in React, it is common to see both react-router and react-router-dom listed in dependencies. Understanding the relationship between these two packages is critical for architectural clarity, especially when setting up testing environments or sharing code across platforms. While they work together, they serve distinct layers of the application stack.
react-router is the core engine.
// react-router: Core components
import { Routes, Route, Navigate } from 'react-router';
// Works in any environment (Web, Native, Server)
<Routes>
<Route path="/home" element={<Home />} />
</Routes>
react-router-dom is the browser adapter.
react-router and re-exports all its components.// react-router-dom: Web-specific components
import { BrowserRouter, Link } from 'react-router-dom';
// Wraps the core with browser history
<BrowserRouter>
<Link to="/home">Home</Link>
</BrowserRouter>
The most visible difference is how you initialize the routing context. The core package provides generic providers, while the DOM package provides the one that syncs with the browser.
react-router uses MemoryRouter or the low-level Router.
MemoryRouter keeps history in memory without changing the URL.// react-router: Memory-based history
import { MemoryRouter } from 'react-router';
<MemoryRouter initialEntries={['/dashboard']}>
<App />
</MemoryRouter>
react-router-dom uses BrowserRouter.
BrowserRouter uses the HTML5 history API to keep UI in sync with the URL.// react-router-dom: Browser history
import { BrowserRouter } from 'react-router-dom';
<BrowserRouter>
<App />
</BrowserRouter>
Navigation components differ based on whether the environment supports user clicks or requires programmatic control.
react-router relies on Navigate for movement.
Navigate is a component that renders nothing but changes location.// react-router: Programmatic redirect
import { Navigate } from 'react-router';
// Redirects user immediately upon render
{!isLoggedIn ? <Navigate to="/login" /> : <Dashboard />}
react-router-dom adds Link for user interaction.
Link renders an <a> tag that prevents full page reloads.Navigate (re-exported from core).// react-router-dom: Declarative link
import { Link } from 'react-router-dom';
// Renders a clickable anchor tag
<Link to="/dashboard">Go to Dashboard</Link>
Hooks are where the packages overlap the most. The DOM package re-exports the core hooks, so they behave identically in a web environment.
react-router exports the base hooks.
useNavigate, useParams, and useLocation live here.// react-router: Importing from core
import { useNavigate } from 'react-router';
function SubmitButton() {
const navigate = useNavigate();
return <button onClick={() => navigate('/success')} />;
}
react-router-dom re-exports the same hooks.
useNavigate from here with no loss of functionality.// react-router-dom: Importing from DOM package
import { useNavigate } from 'react-router-dom';
function SubmitButton() {
const navigate = useNavigate();
return <button onClick={() => navigate('/success')} />;
}
Testing is the primary use case where you might interact with react-router directly, even in a web project.
react-router is ideal for unit tests.
window.history.MemoryRouter allows you to set initial paths without side effects.// react-router: Testing setup
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
render(
<MemoryRouter initialEntries={['/profile']}>
<UserProfile />
</MemoryRouter>
);
react-router-dom is used for integration tests.
BrowserRouter when testing full user flows with URL changes.window.location.// react-router-dom: Integration setup
import { render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
render(
<BrowserRouter>
<UserProfile />
</BrowserRouter>
);
Despite the separation, the two packages share a massive amount of DNA. In a web app, they function as a single unit.
Routes, Route, and Outlet work exactly the same.// Both packages support this identically
import { Routes, Route } from 'react-router'; // or react-router-dom
<Routes>
<Route path="*" element={<NoMatch />} />
</Routes>
useParams, useSearchParams, and useMatch return the same data.// Both return the same URL parameters
import { useParams } from 'react-router'; // or react-router-dom
const { id } = useParams();
// Context is shared regardless of import source
import { useLocation } from 'react-router';
import { Link } from 'react-router-dom';
// These work together seamlessly
| Feature | react-router | react-router-dom |
|---|---|---|
| Environment | ๐ Universal (Web, Native, Server) | ๐ Web Browser Only |
| Router Component | MemoryRouter, Router | BrowserRouter, HashRouter |
| Navigation | Navigate (Programmatic) | Link, NavLink (Declarative) |
| History | In-Memory or Custom | HTML5 History API |
| Primary Use | Libraries, Tests, Shared Logic | Standard Web Applications |
| Dependency | Zero external deps | Depends on react-router |
react-router is the foundation ๐๏ธ โ it holds the logic but doesn't touch the browser. Use it when you need portability, are writing tests, or building tools that other developers will use across different platforms.
react-router-dom is the implementation ๐ฅ๏ธ โ it connects that logic to the web. Use it for your actual website. It includes everything in react-router plus the tools needed to make the URL bar and back button work.
Final Thought: For 99% of web projects, install react-router-dom and import everything from there. Only reach for react-router directly if you have a specific need to decouple your logic from the browser environment.
Choose react-router if you are building a custom router implementation, writing unit tests that require a memory-based history, or sharing routing logic across different React environments like web and native in a monorepo. It is also the correct choice if you need to access low-level routing primitives without pulling in browser-specific dependencies. However, for standard web applications, this package alone is insufficient because it lacks browser history integration.
Choose react-router-dom for virtually all standard web applications running in a browser environment. It re-exports all functionality from react-router and adds essential components like BrowserRouter, Link, and NavLink that interact with the DOM. This package handles the synchronization between your UI and the browser's URL bar, making it the complete solution for client-side routing on the web.
react-router is the primary package in the React Router project.
npm i react-router