Table of Contents
- React Router vs. React Router DOM: The short answer
- Why routing matters once your React app starts growing
- Understanding React Router
- Why do so many older projects use React Router DOM?
- How the relationship changed across versions
- Which package should you install?
- What React Router v8 actually changes for developers
- Choosing the right routing mode for your project
- Nested Routes: The pattern you’ll use most often
- Routing hooks you’ll use every day
- Migrating existing applications without breaking routes
- SEO considerations for React SPAs
- Frequently Asked Questions
- Routing sorted. Time to create exceptional React experiences
- Related Blogs
TL;DR: Confused by conflicting React Router tutorials? Discover why react-router-dom is disappearing, what React Router v8 changes for developers, and how to build or migrate React applications with confidence.
If you’ve searched for “React Router vs React Router DOM” recently, you’ve probably come across conflicting advice.
Some tutorials still tell you to install react-router-dom, while newer examples import everything directly from react-router. If you’re maintaining an older application and starting a new one at the same time, the situation can be even more confusing because both approaches may appear valid.
The reason is simple: React Router’s package structure has changed significantly across recent versions. What was considered best practice in React Router v6 is no longer the recommended approach in React Router v8.
For many developers, this confusion shows up during upgrades. You open an existing project, see imports coming from react-router-dom, start reading the latest documentation, and suddenly discover those imports have moved. Understanding why that happened can save hours of migration headaches later.
In this article, we’ll break down the differences between react-router and react-router-dom, explain how the package structure evolved across versions, look at real-world routing scenarios, and help you choose the right approach for your next React application.

Syncfusion React UI components are the developers’ choice to build user-friendly web applications. You deserve them too.
React Router vs. React Router DOM: The short answer
If you’re looking for a quick recommendation:
| Scenario | Recommended approach | Reason |
| New React application | Install the current react-router release. | It is the primary package. |
| Existing v6 application | Retain react-router-dom during the initial migration. | It provides a compatible v6-to-v7 upgrade path. |
| Existing v7 application | Update to the latest v7 release, migrate remaining imports from react-router-dom to react-router, adopt applicable v8 preparation changes, and then upgrade to v8. | It follows the current package direction. |
| Application upgrading from v7 to v8 | Remove react-router-dom, complete v7 preparation, and upgrade react-router. | The compatibility package is removed in v8. |
| Current v8 application | Use react-router for common routing APIs and react-router/dom for the browser-specific RouterProvider. | The react-router/dom version of RouterProvider automatically integrates React DOM’s flushSync implementation. |
| Application unable to meet the v8 requirements | Remain temporarily on the latest suitable v7 release. | It allows the environment to be upgraded separately. |
The short version is that React Router has become the primary package, while react-router-dom is no longer part of the long-term direction of the framework.
But understanding why this change happened helps make future upgrades much easier.
Why routing matters once your React app starts growing
React renders user interfaces, but it does not include a built-in application router.
We can easily build a React application with a single screen. The real challenge starts when your application evolves into multiple pages, feature areas, and user journeys.
Imagine building an e-commerce platform:
| / → Home /products → Product list /products/42 → Product details /account/settings → Account settings |
Users expect all these URLs to be shareable, bookmarkable, and accessible through browser navigation controls.
Without a routing solution, managing this experience quickly becomes difficult. Routing connects URLs to components, preserves navigation history, handles nested layouts, and enables features such as route-level data loading.
This is where React Router becomes an essential part of most React SPAs.
Understanding React Router
Think of React Router as the routing engine that keeps your application synchronized with the URL.
Instead of manually deciding which component should render for a given path, React Router handles that mapping for you and provides additional capabilities such as:
- URL matching
- Navigation
- Nested routes
- Dynamic route parameters
- Route-level data loading
- Error handling
- Redirect management
In modern React applications, react-router serves as the primary package that exposes these APIs.
For most developers starting a new project today, this is the package you’ll install first.
Why do so many older projects use React Router DOM?
If you’ve worked with React for a few years, you’ve almost certainly seen imports like:
import {
BrowserRouter,
Routes,
Route
} from "react-router-dom";For a long time, this was the standard approach.
Historically:
- react-router contained the routing core.
- react-router-dom provided browser-specific functionality.
This separation made sense for earlier versions of React Router, but it also introduced confusion because web developers usually interacted with the DOM package directly and rarely touched the core package.
As React Router evolved, the maintainers simplified this structure. Starting in v7, react-router-dom became primarily a compatibility layer, and in v8 it was removed entirely.
This means that many older tutorials are still technically correct for older applications, but they no longer represent the recommended approach for new projects.

A to Z about Syncfusion’s versatile React components and their feature set.
How the relationship changed across versions
One of the biggest reasons developers get confused is that the answer depends heavily on the version being used.
A React Router v6 project and a React Router v8 project can look surprisingly different even when they solve the same routing problem.
| Version | react-router | react-router-dom | react-router/dom |
| v6 | Core routing package | Common browser and web package | Not the primary replacement for react-router-dom. |
| v7 | Primary React Router package | Compatibility re-export for the v6 upgrade path | Provides DOM-specific exports such as RouterProvider and HydratedRouter. |
| v8 | Primary package for routing APIs | Removed | DOM-specific exports such as RouterProvider and HydratedRouter. |
Key takeaway: When reading tutorials, always verify which React Router version the article targets before following installation instructions.
Which package should you install?
For a new application, the answer is straightforward:
Install the current react-router package:
npm install react-routerWith pnpm:
pnpm add react-routerWith Yarn:
yarn add react-routerThen, import the required APIs:
import {
BrowserRouter,
Link,
Route,
Routes
} from "react-router";Avoid pinning a new application to react-router@7 unless the project has a verified compatibility reason to remain on that major version.
If the application currently uses React Router v6, follow the staged process in the “Migrating existing application without breaking routes” section rather than replacing the package and imports in a single update.
Choosing the right package is only part of the story. For many teams, the bigger question is what actually changes when moving to React Router v8.
What React Router v8 actually changes for developers
Most articles focus on package removal, but developers usually care about something more practical:
What changes in day-to-day development?
The biggest change is not writing routes differently.
The biggest change is understanding where routing APIs come from and ensuring your application’s runtime meets the newer platform requirements.
For new projects, that means less package confusion.
For existing projects, it means planning upgrades more carefully.
React Router v8 continues the package changes introduced in v7 by removing the react-router-dom compatibility package and consolidating common routing APIs under react-router.
For Data mode in browser applications, RouterProvider can be imported from react-router/dom, which automatically supplies React DOM’s flushSync implementation.
For example:
import {
createBrowserRouter,
useLoaderData
} from "react-router";
import { RouterProvider } from "react-router/dom";RouterProvider is exported from both react-router and react-router/dom. In a browser DOM environment, the react-router/dom export is generally preferred because it automatically supplies React DOM’s flushSync implementation.
One of the most important considerations is the updated runtime and tooling baseline. The current official v8 upgrade guide specifies:
- Node.js 22.22 or later.
- React 19.2.7 or later.
- React DOM 19.2.7 or later.
- Vite 7 or later for Framework mode.
- React Router v8 packages are published as ESM-only packages, so the application’s runtime and tooling must support ESM package consumption.
Before moving from React Router v7 to React Router v8:
- Update the application to the latest suitable v7 release.
- While the application is still on v7, follow the official v7-to-v8 guide and adopt the applicable v8-compatible changes and future-flag behaviors.
- Update Node.js, React, and React DOM.
- Update Vite if the project uses Framework mode.
- Migrate remaining common imports from react-router-dom to react-router.
- In browser-based Data mode applications, import RouterProvider from react-router/dom.
- Remove react-router-dom.
- Test routes, loaders, actions, middleware, rendering, and deployment.
- Upgrade react-router to the current v8 release.
The applicable future-flag behaviors are preparation steps performed while the application remains on v7. In v8, the adopted behaviors are incorporated into the release.
The official migration documentation recommends introducing and shipping these changes incrementally instead of performing the entire upgrade as one unreviewed change.
Version note: React Router 8.3.1 is the current stable release at the time of writing, while React Router 7.18.3 remains the active v7 release line. Verify the latest release and minimum requirements in the official documentation before installation, as patch versions and compatibility requirements can change.

See the possibilities for yourself with live demos of Syncfusion React components.
Choosing the right routing mode for your project
Not every React application needs the same routing model.
A simple dashboard has different routing needs than an e-commerce platform with server rendering, route loaders, and complex data dependencies.
React Router supports three routing approaches:
- Declarative mode
- Data Mode and
- Framework mode.
Let’s look at when each approach makes the most sense.
Declarative Mode
Best when you want straightforward routing and manage data separately.
Declarative mode uses APIs such as BrowserRouter, Routes, and Route for straightforward component-based routing. It is appropriate when an application manages data fetching and mutations separately.
import {
BrowserRouter,
Link,
Route,
Routes
} from "react-router";
function Home() {
return <h1>Home</h1>;
}
function About() {
return <h1>About</h1>;
}
function PageNotFound() {
return (
<section>
<h1>404: Page not found</h1>
<Link to="/">Return to home</Link>
</section>
);
}
function App() {
return (
<BrowserRouter>
<nav aria-label="Main navigation">
<Link to="/">Home</Link>{" "}
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
);
}
export default App;In this example:
- BrowserRouter enables browser-history-based routing.
- Routes select the route that matches the current URL.
- Route connects a path with a React element.
- Link enables navigation without a complete page reload.
- The wildcard route renders a not-found component for unmatched client-side URLs.
Data Mode
Best when routes are responsible for fetching and modifying data.
Data mode uses createBrowserRouter and RouterProvider. It adds route loaders, actions, pending navigation states, route-level error handling, and APIs such as useFetcher.
import {
createBrowserRouter,
useLoaderData
} from "react-router";
import { RouterProvider } from "react-router/dom";
async function loadProduct({ params }) {
const response = await fetch(
`/api/products/${params.productId}`
);
if (!response.ok) {
throw new Response("Product not found", {
status: 404,
});
}
return response.json();
}
function Home() {
return <h1>Home</h1>;
}
function ProductDetails() {
const product = useLoaderData();
return (
<section>
<h2>{product.name}</h2>
<p>{product.description}</p>
</section>
);
}
const router = createBrowserRouter([
{
path: "/",
Component: Home,
},
{
path: "/products/:productId",
Component: ProductDetails,
loader: loadProduct,
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;Use BrowserRouter for Declarative mode and createBrowserRouter with RouterProvider for Data mode. Do not wrap RouterProvider inside BrowserRouter, as these APIs establish different top-level router configurations.
Framework Mode
Best when you want additional tooling, route-based code splitting, server rendering options, and stronger project conventions.
Framework mode builds on Data mode with Vite-based tooling and project conventions. It provides route modules, type-safe APIs, automatic route-level code splitting, and SPA, server-rendering, and static-rendering strategies.
Choose:
- Declarative mode for straightforward routing with separately managed data.
- Data mode for loaders, actions, pending states, and route-level data handling.
- Framework mode when the project needs additional tooling, conventions, code splitting, and rendering integration.
Rather than asking which mode is “best,” ask which mode solves the problems your application actually has.
Nested Routes: The pattern you’ll use most often
Once an application grows beyond a handful of pages, nested routing becomes one of the most valuable features in React Router.
Think about a product catalog.
Every product page might share:
- Navigation
- Sidebar
- Header
- Breadcrumbs
Instead of repeating that layout everywhere, nested routes allow parent layouts to remain consistent while child routes render their own content.
import {
BrowserRouter,
Link,
Outlet,
Route,
Routes,
useParams
} from "react-router";
function ProductLayout() {
return (
<section>
<h1>Products</h1>
<Link to="1001">View product 1001</Link>
<Outlet />
</section>
);
}
function ProductDetails() {
const { productId } = useParams();
if (!productId) {
return <p>Product ID is missing.</p>;
}
return <h2>Product ID: {productId}</h2>;
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/products" element={<ProductLayout />}>
<Route
path=":productId"
element={<ProductDetails />}
/>
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;In this example, Outlet indicates where the matched child route should render, while :productId defines a dynamic segment that can match URLs such as /products/1001.
Validate required route parameters before passing them to APIs or using them in business logic.
This pattern becomes especially useful in admin dashboards, e-commerce platforms, and SaaS applications where large sections of the UI remain unchanged during navigation.
Routing hooks you’ll use every day
Most developers spend far more time interacting with routing hooks than configuring routes.
These hooks provide access to the information your components need most often:
useNavigate()performs programmatic navigation.useParams()reads dynamic route segments.useLocation()returns the current pathname, search string, hash, and navigation state.useMatch()checks whether the current URL matches a specified route pattern and returns the corresponding match information when it does.
import {
useNavigate,
useParams,
useLocation,
useMatch
} from 'react-router';
function UserProfile() {
const navigate = useNavigate();
const { id } = useParams();
const location = useLocation();
const match = useMatch('/user/:id');
return (
<section>
<h2>User Profile: {id}</h2>
<p>Current Path: {location.pathname}</p>
<p>Route Match: {match ? 'Matched' : 'Not matched'}</p>
<button onClick={() => navigate('/home')}>Go Home</button>
</section>
);
}These hooks are available across the applicable React Router modes.
Route components can also be loaded on demand with React’s lazy and Suspense. Framework mode provides additional route-level code-splitting capabilities through its Route Module architecture.
Rather than manually parsing URLs, these hooks provide a cleaner and more maintainable way to work with route data.
A real-world migration example
Consider a SaaS dashboard application with:
- User management
- Billing
- Reporting
- Settings
- Analytics pages
The project has grown over several years and currently runs on React Router v6.
Attempting to upgrade directly to v8 could mean changing route imports across dozens of modules simultaneously.
Instead, teams can:
- Upgrade to v7.
- Keep the compatibility package temporarily.
- Move imports incrementally.
- Test each feature area independently.
- Remove compatibility dependencies later.
This approach dramatically reduces risk because routing issues become easier to isolate and fix.
For larger teams, this staged migration strategy is often much safer than a single large upgrade.
Migrating existing applications without breaking routes
The safest migrations are usually the least exciting ones.
Instead of treating a React Router upgrade as a massive refactor, think of it as a series of small, testable changes.
A React Router migration becomes significantly easier when package upgrades and routing changes are introduced incrementally.
A staged migration approach helps you:
- Reduce deployment risk
- Isolate routing issues faster
- Validate navigation behavior incrementally
- Avoid combining architectural changes with package upgrades
Step 1: Prepare the v6 application
Update the application to the latest suitable v6 release and review:
- Route configuration.
- Navigation hooks.
- Nested and dynamic routes.
- Loaders and actions.
- Error boundaries.
- Form submissions.
- Automated tests.
Step 2: Upgrade to React Router v7
Install the current v7 release:
npm install react-router@7If the application still imports APIs from react-router-dom, the v7 compatibility package can temporarily remain because it re-exports APIs from react-router. Use this compatibility period to move imports from react-router-dom to react-router.
Step 3: Test the application
Verify direct URL navigation, browser history, route parameters, nested routes, loaders, actions, form submissions, not-found routes, and error states.
Step 4: Move imports to react-router
With the application running on v7, begin moving common routing imports to react-router to align with the v8 package structure.
import {
Link,
Route,
Routes
} from "react-router-dom";
import {
Link,
Route,
Routes
} from "react-router";Step 5: Remove react-router-dom
Once the imports have been updated and tested, remove the compatibility package from the project.
npm uninstall react-router-domStep 6: Prepare the v7 application for v8
After stabilizing v7:
- Update to the latest suitable v7 release.
- Follow the official v7-to-v8 guide while the application is still on v7.
- Adopt the applicable v8-compatible changes and future-flag behaviors described for the v7 preparation stage.
- Confirm that the project meets the Node.js, React, React DOM, and Framework-mode Vite requirements.
- If the application uses Data mode in a browser environment, update its RouterProvider import to react-router/dom.
- Confirm that react-router-dom is no longer installed or imported.
- Upgrade react-router to the current v8 release and retest the application.
Avoid common migration mistakes:
- Use Routes instead of the older Switch API.
- Do not wrap
RouterProviderinside BrowserRouter. - Avoid changing package versions and routing architecture in the same untested update.
- Client-side protected routes do not replace backend authentication and authorization.
React Router v8 is designed as a relatively low-friction upgrade from a properly prepared v7 application. Most migration work can be completed while remaining on v7 by adopting the recommended preparation changes before upgrading.
SEO considerations for React SPAs
Routing and SEO are closely related, but they’re not the same thing.
A common misconception is that installing React Router automatically improves search visibility.
It doesn’t.
What React Router does provide is a clean routing foundation. Strong SEO still depends on factors such as metadata, rendering strategy, crawlability, and server responses.
If organic search traffic is important for your application, consider routing decisions alongside your rendering approach rather than treating them as separate concerns.
Follow these practices:
- Use descriptive, stable URLs such as /products/electronics instead of unclear parameter combinations.
- Provide a relevant title, meta description, and canonical URL for each indexable route.
- Consider server or static rendering when important route content needs to be delivered in the initial HTML, or when SEO and social-preview requirements make client-side rendering alone insufficient.
- Return correct HTTP status codes for missing pages and redirects in server-rendered applications.
- Use Link or standard anchor elements so important destinations remain crawlable.
A client-side wildcard route can display a not-found page, but it does not necessarily cause the server to return a 404 HTTP status. Rendering, metadata, crawlability, canonicalization, and server responses must be implemented according to the application’s SEO requirements.
Frequently Asked Questions
React Router can be added to a React project built with Vite by installing react-router. The official guide uses a Vite-based React template. Framework mode includes extra tooling and a Vite plugin, while Declarative and Data modes work with existing Vite apps.Does React Router work with Vite?
Use Use For most applications with configurable hosting, When should I use HashRouter instead of BrowserRouter?
BrowserRouter when the web server can direct application URLs to the React entry page. It uses the browser History API and creates standard URLs such as /products/42.HashRouter when the hosting environment cannot be configured to handle client-side routes. It stores the route after the # symbol, as in /#/products/42, and the hash portion is not sent to the server.BrowserRouter provides cleaner URLs.
No. Simple SPAs with a single view may not need routing. React Router is useful when your app requires unique URLs, browser navigation, deep links, nested routes, route parameters, loaders, actions, or route-level error handling.Does every React SPA need React Router?
Use the How do you manage query parameters in React Router?
useSearchParams hook to read and update URL query parameters. Updating search parameters automatically navigates to the new URL.
Yes. React Router supports the View Transitions API in supported browsers for Data and Framework modes. Enable it with the Does React Router support animated page transitions?
viewTransition prop on Link, NavLink, or Form, or by passing { viewTransition: true } to useNavigate().

Explore the endless possibilities with Syncfusion’s outstanding React UI components.
Routing sorted. Time to create exceptional React experiences
For years, React developers automatically installed react-router-dom without giving it much thought. Today, that habit can create unnecessary confusion because React Router’s package structure has evolved significantly.
For new applications, the decision is simple: start with react-router and follow the current package model. If you’re maintaining an older v6 or v7 application, focus on a gradual migration strategy rather than attempting a large one-time upgrade.
More importantly, think beyond package names. Choosing between Declarative, Data, and Framework modes will often have a bigger impact on your application’s architecture than deciding where an import comes from.
Understanding these changes now can save substantial migration effort later, especially as React Router continues moving toward a more streamlined and modern developer experience.
Build better React experiences beyond routing
After solving routing, many teams focus on the next set of challenges: handling large datasets, visualizing information, generating documents, and building complex UI interactions. Syncfusion React UI Components provide ready-to-use controls that help reduce custom implementation work and accelerate development.
Explore Syncfusion React UI Components and accelerate your next React project!
Already upgraded to the latest React Router version? Share your migration experience and lessons learned in the comments.
