react-router is the core library for handling navigation in React applications. connected-react-router, react-router-redux, and redux-first-history are bindings that synchronize routing state with a Redux store, allowing navigation to be managed via Redux actions or state selectors. redux-logger is a middleware utility used to log Redux actions and state changes to the console, aiding in debugging the flow of data including routing updates. Together, these tools represent different approaches to managing location state and observing changes within the Redux ecosystem.
Building complex React applications often involves deciding how to handle navigation state. While react-router is the standard for rendering views based on URL, teams using Redux sometimes need to sync that location state with their global store. This comparison breaks down the core routing library, the Redux bindings, and the debugging tools available.
react-router is the essential package for any React app needing navigation. It provides the components and hooks to render different views based on the browser's URL. You do not need Redux to use this library effectively in most modern applications.
// react-router: Standard usage with hooks
import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
function Home() {
const navigate = useNavigate();
return <button onClick={() => navigate('/about')}>Go</button>;
}
When you need to trigger navigation from Redux thunks or access location state in selectors, you need a binding library. Here is how the options differ.
connected-react-routerThis is the standard solution for React Router v4 and v5. It wraps your router and syncs history changes to the Redux store automatically.
// connected-react-router: v5 setup
import { ConnectedRouter } from 'connected-react-router';
import { push } from 'connected-react-router';
// In your component
<ConnectedRouter history={history}>
<Routes>...</Routes>
</ConnectedRouter>
// In your Redux action
dispatch(push('/new-path'));
react-router-reduxThis package is deprecated. It was the original attempt to bind React Router and Redux but is no longer safe or compatible with modern versions. You should not use this in any new code.
// react-router-redux: DEPRECATED - Do not use
// Legacy code might look like this, but it is obsolete
import { routerReducer } from 'react-router-redux';
// This API is no longer maintained and lacks v6 support
redux-first-historyThis library flips the model. Instead of listening to history changes, it lets you dispatch actions to change the route. It treats navigation purely as a state update.
// redux-first-history: Action-driven navigation
import { push } from 'redux-first-history';
// In your Redux thunk
export const goToDashboard = () => async (dispatch) => {
await dispatch(push('/dashboard'));
};
// In your store setup
import { createReduxHistoryContext } from 'redux-first-history';
// Requires specific store configuration to link history
redux-logger is not a routing library. It is a middleware that prints every Redux action and the resulting state to the console. It helps you see when routing actions fire.
// redux-logger: Middleware setup
import { createLogger } from 'redux-logger';
import { configureStore } from '@reduxjs/toolkit';
const logger = createLogger();
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
});
| Feature | react-router | connected-react-router | react-router-redux | redux-first-history | redux-logger |
|---|---|---|---|---|---|
| Primary Role | Core Routing | Redux Sync (v5) | Redux Sync (Legacy) | Action-Driven Routing | Debugging |
| Maintenance | β Active | β οΈ Maintenance (v5) | β Deprecated | β οΈ Low Activity | β Active |
| Redux Required | β No | β Yes | β Yes | β Yes | β Yes |
| React Router v6 | β Native Support | β οΈ Complex/Unofficial | β No Support | β οΈ Limited Support | β Compatible |
| Navigation Trigger | Hooks/Components | Actions/Selectors | Actions (Legacy) | Actions Only | N/A |
The landscape changed significantly with React Router v6. The core library now uses React Context heavily, which is fast enough for most apps without Redux sync.
react-router is the only package with full, native support for v6 features like useNavigate and loader data APIs.connected-react-router has limited v6 support. Many teams are moving away from syncing router state to Redux in v6 because it adds complexity without much benefit.react-router-redux should never be used. It is dead software.redux-first-history can work with v6 but requires careful configuration to match the new history API.redux-logger works with any version but adds bundle size. Only include it in development builds.You are building a marketing site or blog with simple navigation.
react-router// Standard react-router implementation
<Routes>
<Route path="/" element={<Home />} />
</Routes>
You maintain an existing app using React Router v5 and Redux, needing to track history in state for undo/redo features.
connected-react-router// connected-react-router usage
const location = useSelector((state) => state.router.location);
Your team requires every state change, including navigation, to happen via explicit dispatches for auditing.
redux-first-history// redux-first-history usage
dispatch(push('/audit-log'));
You are troubleshooting why a route change isn't triggering a data fetch.
redux-logger// redux-logger output
// ACTION: @router/LOCATION_CHANGE
// PREV STATE: { location: '/' }
// NEXT STATE: { location: '/about' }
react-router is the mandatory foundation for navigation in React. Use it for all projects.
connected-react-router is the viable option if you must sync routing with Redux on React Router v5. For v6, reconsider if you really need Redux sync at all.
react-router-redux is deprecated and must be avoided. Replace it with connected-react-router or remove Redux routing bindings entirely.
redux-first-history is a niche tool for action-driven architectures. Use it only if your team specifically requires navigation to be dispatched as actions.
redux-logger is a development tool. Use it to watch the flow of routing actions but exclude it from production builds to save performance.
Final Thought: Modern React development favors keeping routing state local to the router library unless you have a specific need for global access. Start with react-router alone and only add Redux bindings if your architecture demands it.
Choose connected-react-router if you are using React Router v5 and need to access routing state directly from Redux reducers or middleware. It is the maintained successor to react-router-redux and provides stable bindings for syncing history with the store. Avoid this for new React Router v6 projects unless you have a specific need to store location state in Redux, as v6 hooks often make this unnecessary.
Choose react-router for all standard React applications that require navigation, regardless of whether you use Redux. It is the foundational library that provides components like BrowserRouter and hooks like useNavigate. You should always install this package as it is the core dependency for the other routing bindings listed here.
Do not choose react-router-redux for any new project as it is officially deprecated and no longer maintained. It was the original binding library but was superseded by connected-react-router years ago. Using it introduces security risks and compatibility issues with modern React versions.
Choose redux-first-history if you prefer a unidirectional data flow where navigation is triggered strictly by dispatching Redux actions rather than listening to history changes. This approach is useful if your architecture requires all state changes, including route changes, to originate from explicit dispatches. It is less common than connected-react-router but offers tighter control over action-driven navigation.
Choose redux-logger during development to inspect Redux actions and state changes in the browser console. It is not required for production builds but helps debug how routing actions or other state updates flow through your middleware pipeline. Pair this with any of the routing bindings to visualize when navigation events occur.
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.