react-router provides the core routing logic for React applications, while react-router-dom adds specific bindings for web browsers including components like BrowserRouter and Link. connected-react-router and mobx-react-router are third-party bridges that synchronize routing state with Redux and MobX stores, respectively. These integration packages allow developers to access routing information directly from their global state management system, though modern patterns often favor direct hook usage.
When building complex React applications, routing is rarely just about changing URLs. It involves managing history, protecting routes, and often syncing navigation state with global data stores. The ecosystem splits into core logic, platform bindings, and state management integrations. Let's break down how react-router, react-router-dom, connected-react-router, and mobx-react-router fit together — and where the industry is moving.
The foundation of routing in React is split between platform-agnostic logic and web-specific implementations.
react-router contains the core components and hooks that work across different environments.
// react-router: Core components only
import { Router, Routes, Route } from 'react-router';
// You must provide your own history object
function App({ history }) {
return (
<Router history={history}>
<Routes>...</Routes>
</Router>
);
}
react-router-dom builds on top of react-router for web browsers.
react-router.BrowserRouter, HashRouter, Link, and NavLink.// react-router-dom: Web-specific bindings
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav><Link to="/about">About</Link></nav>
<Routes>...</Routes>
</BrowserRouter>
);
}
In large applications, you might need to access the current path or navigation actions from outside the component tree — for example, inside a Redux saga or a MobX action. This is where integration packages come in.
connected-react-router connects React Router to a Redux store.
ConnectedRouter.// connected-react-router: Redux integration
import { ConnectedRouter } from 'connected-react-router';
import { useSelector } from 'react-redux';
function App({ history }) {
return (
<ConnectedRouter history={history}>
{/* Router state is now in redux state.router */}
</ConnectedRouter>
);
}
function Nav() {
// Accessing location from Redux store
const location = useSelector(state => state.router.location);
return <div>{location.pathname}</div>;
}
mobx-react-router connects React Router to a MobX store.
router store that observes navigation changes.observer to react to route changes.// mobx-react-router: MobX integration
import { RouterStore } from 'mobx-react-router';
import { observer } from 'mobx-react';
const routingStore = new RouterStore();
const Nav = observer(() => {
// Accessing location from MobX store
return <div>{routingStore.location.pathname}</div>;
});
// Setup requires syncing history to the store
routingStore.syncHistoryWithStore(history);
The industry has shifted away from syncing router state to global stores unless absolutely necessary. React Router v6 introduced powerful hooks that reduce the need for connected-react-router or mobx-react-router.
Direct Hook Usage (Recommended)
useLocation, useNavigate, and useParams directly in components.// Modern React Router v6 approach (No store sync needed)
import { useLocation, useNavigate } from 'react-router-dom';
function Nav() {
const location = useLocation();
const navigate = useNavigate();
return (
<button onClick={() => navigate('/home')}>
Current: {location.pathname}
</button>
);
}
When Store Sync Is Still Useful
state.router selectors.// Legacy/Specific Case: Accessing navigation in Redux Saga
// Requires connected-react-router to populate state
function* watchLocation() {
yield takeLatest('@@router/LOCATION_CHANGE', function*(action) {
// Perform side effect based on route change
yield call(fetchData, action.payload.location.pathname);
});
}
One of the biggest risks with integration packages is version drift. react-router-dom moves fast, while integration libraries often lag behind.
react-router-dom: Actively maintained. Supports React 18 and Concurrent features.react-router: Actively maintained. Core logic updates alongside DOM package.connected-react-router: Maintenance has slowed. Many teams are migrating away from it due to v6 incompatibilities.mobx-react-router: Niche maintenance. Verify compatibility with your specific MobX and React Router versions before adopting.| Feature | react-router-dom | react-router | connected-react-router | mobx-react-router |
|---|---|---|---|---|
| Primary Use | Web Applications | Core/Library | Redux Sync | MobX Sync |
| Includes DOM | ✅ Yes | ❌ No | ❌ No (Requires history) | ❌ No (Requires history) |
| State Sync | ❌ Local Context | ❌ Local Context | ✅ Redux Store | ✅ MobX Store |
| React Router v6 | ✅ Full Support | ✅ Full Support | ⚠️ Limited/Legacy | ⚠️ Limited/Legacy |
| Boilerplate | Low | Medium | High (Reducers/Middleware) | Medium (Store Setup) |
react-router-dom is the standard choice for 95% of web projects. It provides all the tools you need without external dependencies. Start here.
react-router is a dependency of react-router-dom. You only install it separately if you are building a custom history implementation or targeting non-DOM platforms.
connected-react-router and mobx-react-router solve a specific problem — global access to routing state — but they introduce coupling and maintenance overhead. In modern React development, we prefer lifting routing logic into components using hooks, or using event listeners for side effects, rather than syncing the entire router state to a global store.
Final Thought: Keep your routing local unless you have a proven need for global access. This keeps your architecture flexible and your dependencies minimal.
Choose connected-react-router only if you have a legacy React Router v5 codebase with heavy Redux dependencies that require time-travel debugging or complex selectors based on route state. For new projects, avoid this package as React Router v6 encourages keeping routing state local to the router.
Choose mobx-react-router if you are maintaining an existing MobX-based application that relies on observable router state. For new architectures, consider using standard React Router hooks within MobX observers instead, as this reduces external dependencies and alignment issues.
Choose react-router only if you are building a non-DOM environment (like React Native via react-router-native) or authoring a library that needs to remain platform-agnostic. For standard web apps, this package is implicitly included via react-router-dom and rarely installed directly.
Choose react-router-dom for virtually all standard web applications. It includes everything from react-router plus the necessary DOM-specific components like BrowserRouter, Link, and useNavigate. This is the default starting point for any new React web project.
Breaking change in v5.0.0! Please read How to migrate from v4 to v5/v6.
v6.0.0 requires React v16.4.0 and React Redux v6.0 / v7.0.
A Redux binding for React Router v4 and v5
:sparkles: Synchronize router state with redux store through uni-directional flow (i.e. history -> store -> router -> components).
:gift: Supports React Router v4 and v5.
:sunny: Supports functional component hot reloading while preserving state (with react-hot-reload).
:tada: Dispatching of history methods (push, replace, go, goBack, goForward) works for both redux-thunk and redux-saga.
:snowman: Nested children can access routing state such as the current location directly with react-redux's connect.
:clock9: Supports time traveling in Redux DevTools.
:gem: Supports Immutable.js
:muscle: Supports TypeScript
Connected React Router requires React 16.4 and React Redux 6.0 or later.
npm install --save connected-react-router
Or
yarn add connected-react-router
In your root reducer file,
history as an argument and returns a root reducer.router reducer into root reducer by passing history to connectRouter.router.// reducers.js
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'
const createRootReducer = (history) => combineReducers({
router: connectRouter(history),
... // rest of your reducers
})
export default createRootReducer
When creating a Redux store,
history object.history to the root reducer creator.routerMiddleware(history) if you want to dispatch history actions (e.g. to change URL with push('/path/to/somewhere')).// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
...
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // root reducer with router state
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
// ... other middlewares ...
),
),
)
return store
}
ConnectedRouter and pass the history object as a prop. Remember to delete any usage of BrowserRouter or NativeRouter as leaving this in will cause problems synchronising the state.ConnectedRouter as a child of react-redux's Provider.StaticRouter from react-router on the server.// index.js
...
import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4/v5
import { ConnectedRouter } from 'connected-react-router'
import configureStore, { history } from './configureStore'
...
const store = configureStore(/* provide initial state if any */)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<> { /* your usual react-router v4/v5 routing */ }
<Switch>
<Route exact path="/" render={() => (<div>Match</div>)} />
<Route render={() => (<div>Miss</div>)} />
</Switch>
</>
</ConnectedRouter>
</Provider>,
document.getElementById('react-root')
)
Note: the history object provided to router reducer, routerMiddleware, and ConnectedRouter component must be the same history object.
Now, it's ready to work!
See the examples folder
npm run build
Generated files will be in the lib folder.
When testing the example apps with npm link or yarn link, you should explicitly provide the same Context to both Provider and ConnectedRouter to make sure that the ConnectedRouter doesn't pick up a different ReactReduxContext from a different node_modules folder.
In index.js.
...
import { Provider, ReactReduxContext } from 'react-redux'
...
<Provider store={store} context={ReactReduxContext}>
<App history={history} context={ReactReduxContext} />
</Provider>
...
In App.js,
...
const App = ({ history, context }) => {
return (
<ConnectedRouter history={history} context={context}>
{ routes }
</ConnectedRouter>
)
}
...
See Contributors and Acknowledge.