react-router-dom and vue-router are the standard routing libraries for React and Vue applications respectively. They enable single-page applications (SPAs) to manage navigation without full page reloads by syncing the URL with the UI state. Both libraries support nested routes, dynamic parameters, history management, and lazy loading. While react-router-dom leans heavily on React hooks and JSX composition, vue-router integrates tightly with Vue's reactivity system and configuration objects. Choosing between them depends primarily on your chosen UI framework, but understanding their architectural differences helps in designing scalable navigation systems.
Both react-router-dom and vue-router are essential tools for building single-page applications in their respective ecosystems. They handle URL changes, manage history, and render components based on the current path without refreshing the browser. However, they approach routing from different angles β one uses React composition patterns while the other uses Vue configuration objects. Let's compare how they tackle common routing problems.
react-router-dom defines routes using JSX components nested inside a Routes wrapper.
// react-router-dom: JSX-based routes
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
vue-router defines routes using a JavaScript array of objects passed to the router instance.
// vue-router: Config-based routes
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
];
const router = createRouter({
history: createWebHistory(),
routes
});
react-router-dom provides hooks like useNavigate to change paths programmatically.
// react-router-dom: Programmatic navigation
import { useNavigate } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const handleLogin = () => {
// Logic here
navigate('/dashboard');
};
return <button onClick={handleLogin}>Login</button>
}
vue-router provides the useRouter composition API function to access the router instance.
push or replace on the router object.// vue-router: Programmatic navigation
import { useRouter } from 'vue-router';
export default {
setup() {
const router = useRouter();
const handleLogin = () => {
// Logic here
router.push('/dashboard');
};
return { handleLogin };
}
}
react-router-dom uses the useParams hook to extract values from the URL.
:id).// react-router-dom: Reading params
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
return <div>User ID: {userId}</div>;
}
// Route: <Route path="/users/:userId" element={<UserProfile />} />
vue-router uses the useRoute composition API function to access the current route state.
:id).// vue-router: Reading params
import { useRoute } from 'vue-router';
export default {
setup() {
const route = useRoute();
return () => <div>User ID: {route.params.userId}</div>;
}
}
// Route: { path: '/users/:userId', component: UserProfile }
react-router-dom often uses wrapper components or loader redirections to protect routes.
PrivateRoute component that checks auth state.loader functions before rendering.// react-router-dom: Protected route wrapper
function PrivateRoute({ children }) {
const isAuth = useAuth();
return isAuth ? children : <Navigate to="/login" />;
}
// Usage
<Route path="/dashboard" element={
<PrivateRoute><Dashboard /></PrivateRoute>
} />
vue-router uses global or per-route navigation guards to check permissions.
beforeEach function on the router instance.next(false) or redirect with next('/login').// vue-router: Global navigation guard
router.beforeEach((to, from, next) => {
const isAuth = checkAuth();
if (to.meta.requiresAuth && !isAuth) {
next('/login');
} else {
next();
}
});
react-router-dom relies on React.lazy and Suspense for code splitting.
React.lazy.Suspense boundary to show fallback UI.// react-router-dom: Lazy loading
import { lazy, Suspense } from 'react';
const About = lazy(() => import('./About'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
vue-router supports lazy loading directly in the route configuration using dynamic imports.
component field.// vue-router: Lazy loading
const routes = [
{
path: '/about',
component: () => import('./About.vue')
}
];
While the implementation details differ, both libraries solve the same core problems with similar capabilities. Here are key overlaps:
// react-router-dom: History type
<BrowserRouter> // Uses HTML5 history API
// vue-router: History type
createWebHistory() // Uses HTML5 history API
// react-router-dom: Nested routes
<Route path="/dashboard" element={<Layout />}>
<Route path="stats" element={<Stats />} />
</Route>
// vue-router: Nested routes
{
path: '/dashboard',
component: Layout,
children: [
{ path: 'stats', component: Stats }
]
}
// react-router-dom: Link component
<Link to="/about">About</Link>
// vue-router: RouterLink component
<router-link to="/about">About</router-link>
// react-router-dom: Redux integration
// Dispatch actions based on route changes via hooks
// vue-router: Pinia integration
// Use route params inside Pinia stores via setup stores
// react-router-dom: Typed params
const { userId } = useParams<{ userId: string }>();
// vue-router: Typed route
const route = useRoute<'UserProfile'>();
| Feature | Shared by React Router and Vue Router |
|---|---|
| Core Tech | π Client-side routing, History API |
| Navigation | π Declarative links, Programmatic push |
| Performance | β‘ Lazy loading, Code splitting |
| Structure | ποΈ Nested routes, Dynamic segments |
| Developer Tools | β TypeScript support, DevTools extension |
| Ecosystem | π₯ Active communities & plugins |
| Feature | react-router-dom | vue-router |
|---|---|---|
| Route Definition | π§© JSX Components (<Route>) | π Config Object (routes: []) |
| Navigation API | πͺ Hooks (useNavigate) | ποΈ Instance Methods (router.push) |
| Param Access | πͺ Hook (useParams) | πͺ Hook (useRoute().params) |
| Protection | π‘οΈ Wrapper Components or Loaders | π‘οΈ Global/Local Guards |
| Lazy Loading | β‘ React.lazy + Suspense | β‘ Dynamic Import in Config |
| Data Loading | π₯ Built-in loader (v6.4+) | π₯ Manual (Setup or Guards) |
react-router-dom is like a set of building blocks π§±βgreat for teams that want to compose routing logic directly into their React component tree. Ideal for apps that benefit from JSX-based configuration and want to leverage the new data loading APIs.
vue-router is like a centralized control panel ποΈβperfect for teams who prefer separating route configuration from UI components. Shines in projects that need robust global guards and straightforward lazy loading setup.
Final Thought: Despite their different approaches, both libraries deliver reliable navigation for modern web apps. Choose based on your UI framework β React teams should stick with react-router-dom, and Vue teams should use vue-router.
Choose react-router-dom if you are building an application with React and need a routing solution that feels like native React code. It is ideal for teams that prefer defining routes directly within JSX components or want to leverage the newer data loading APIs introduced in version 6.4. This package works best when you want tight integration with React context and hooks for managing navigation state. It is also a strong fit if you plan to migrate towards a full-stack framework like Remix later, as they share similar data loading concepts.
Choose vue-router if you are building an application with Vue 3 and want a router that integrates seamlessly with the Vue ecosystem. It is ideal for projects that prefer a centralized configuration object for defining routes rather than spreading them across JSX files. This package works best when you need powerful global navigation guards that are easy to set up without extra wrappers. It is also a strong fit if you value built-in support for component-based lazy loading directly within the route definition.
This package simply re-exports everything from react-router to smooth the upgrade path for v6 applications. Once upgraded you can change all of your imports and remove it from your dependencies:
-import { Routes } from "react-router-dom"
+import { Routes } from "react-router"