connected-react-router vs react-router-redux vs redux-first-history vs redux-first-router vs redux-logger vs redux-saga
Integrating Routing and Side Effects with Redux in React Applications
connected-react-routerreact-router-reduxredux-first-historyredux-first-routerredux-loggerredux-sagaSimilar Packages:

Integrating Routing and Side Effects with Redux in React Applications

connected-react-router, react-router-redux, redux-first-history, and redux-first-router are libraries designed to synchronize browser history and URL state with a Redux store, enabling route changes to be treated as actions and allowing navigation to be driven by Redux dispatches. redux-logger is a middleware that logs Redux actions and state changes for debugging purposes. redux-saga is a middleware for managing complex side effects like data fetching, caching, and asynchronous control flows using generator functions. Together, these packages address routing integration, debugging visibility, and side-effect orchestration in Redux-based React applications.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
connected-react-router04,687444 kB176-MIT
react-router-redux07,737-110 years agoMIT
redux-first-history045499.3 kB183 years agoMIT
redux-first-router01,550335 kB21-MIT
redux-logger05,714-589 years agoMIT
redux-saga0-36.8 kB-a month agoMIT

Integrating Routing and Side Effects with Redux: A Practical Guide

When building React apps with Redux, developers often face three core challenges: keeping URL state in sync with the store, debugging state changes, and managing complex asynchronous workflows. The packages in this comparison address these needs — but they serve very different roles. Let’s clarify what each does, how they work, and when to use them.

🧭 Routing + Redux: Four Approaches Compared

Only four of these packages deal with routing: connected-react-router, react-router-redux, redux-first-history, and redux-first-router. They aim to make browser navigation part of the Redux flow — so you can dispatch { type: 'NAVIGATE', payload: '/dashboard' } and have the URL update accordingly.

react-router-redux: Deprecated — Do Not Use

This package was built for React Router v3 and is officially deprecated. Its npm page states: “This project is no longer maintained.” It does not support React Router v4+, and using it today will lead to compatibility issues.

// ❌ Avoid — outdated and unmaintained
import { routerReducer, routerMiddleware } from 'react-router-redux';

If you see this in a legacy codebase, migrate to connected-react-router.

connected-react-router: The Standard for React Router + Redux

This is the go-to solution for syncing React Router (v4+) with Redux. It provides a reducer for location state, middleware to intercept navigation actions, and a ConnectedRouter component that wraps your app.

// ✅ Recommended for React Router users
import { connectRouter, routerMiddleware } from 'connected-react-router';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

const store = createStore(
  combineReducers({
    router: connectRouter(history),
    // ...other reducers
  }),
  applyMiddleware(routerMiddleware(history))
);

// In your app:
<Provider store={store}>
  <ConnectedRouter history={history}>
    <App />
  </ConnectedRouter>
</Provider>

// Navigate via Redux:
dispatch(push('/profile'));

It integrates cleanly with Redux DevTools, so time-travel debugging includes route changes.

redux-first-history: Framework-Agnostic History Binding

Unlike connected-react-router, this package doesn’t depend on React Router. Instead, it binds any history instance directly to Redux. This makes it useful in non-React apps or when you’re using a custom router.

// ✅ Good for non-React or minimal routing setups
import { createReduxHistoryContext } from 'redux-first-history';
import { createBrowserHistory } from 'history';

const { createReduxHistory, routerReducer, routerMiddleware } = createReduxHistoryContext({
  history: createBrowserHistory()
});

const store = createStore(
  combineReducers({
    router: routerReducer,
    // ...other reducers
  }),
  applyMiddleware(routerMiddleware)
);

const history = createReduxHistory(store);

// Navigate:
history.push('/settings');
// Or dispatch:
dispatch({ type: '@@router/CALL_HISTORY_METHOD', payload: { method: 'push', args: ['/settings'] } });

It’s more flexible but requires manual handling of navigation triggers if you’re not using React Router components.

redux-first-router: Not Recommended

This package lacks recent updates, comprehensive documentation, or clear examples in its repository. It appears to be an experimental or abandoned project. There’s no evidence of active maintenance or community adoption.

// ⚠️ Avoid — unclear API, likely unmaintained
import { routerReducer } from 'redux-first-router';
// No official usage pattern documented

Unless you’re maintaining a legacy system that already uses it, choose a supported alternative.

📝 Debugging with redux-logger

redux-logger is unrelated to routing — it’s a middleware that logs every action and state change to the console. It’s purely for development visibility.

// ✅ Essential for debugging Redux flows
import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';

const store = createStore(reducer, applyMiddleware(logger));

// When you dispatch:
dispatch({ type: 'FETCH_USER_SUCCESS', payload: { id: 1, name: 'Alice' } });

// Console shows:
// prev state → action → next state

It works alongside any other middleware (like redux-saga) and should be excluded from production builds.

⚙️ Managing Side Effects with redux-saga

redux-saga handles asynchronous logic using generator functions. It’s powerful for complex workflows like polling, debouncing, or coordinating multiple API calls.

// ✅ Ideal for complex async logic
import { call, put, takeEvery, fork } from 'redux-saga/effects';
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';

function* fetchUser(action) {
  try {
    const user = yield call(api.getUser, action.payload.userId);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', error });
  }
}

function* watchFetchUser() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUser);
}

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchFetchUser);

Unlike simpler solutions like redux-thunk, sagas are highly testable and support advanced control flow (e.g., canceling ongoing requests).

🔍 Key Trade-Offs Summarized

PackagePurposeReact Router Required?Actively Maintained?Best For
connected-react-routerSync React Router with Redux✅ Yes✅ YesApps using React Router v4+ needing Redux-driven navigation
react-router-reduxLegacy React Router + Redux✅ (v3 only)❌ No (deprecated)Legacy systems only
redux-first-historyBind any history to Redux❌ No✅ YesNon-React apps or custom routing setups
redux-first-routerUnclear / experimental?❌ Likely notAvoid in new projects
redux-loggerLog Redux actions/state❌ No✅ YesDevelopment-time debugging
redux-sagaManage complex async side effects❌ No✅ YesLarge apps with intricate data-fetching or workflow logic

💡 Final Guidance

  • For most React + Redux apps using React Router: Use connected-react-router + redux-saga + redux-logger in development.
  • If you’re not using React Router: Consider redux-first-history for Redux-controlled navigation.
  • Never start a new project with react-router-redux or redux-first-router — they are either deprecated or unmaintained.
  • Always pair redux-saga with redux-logger during development to trace async action flows.

These tools solve distinct problems. Don’t treat them as direct competitors — instead, combine them thoughtfully based on your app’s architecture and requirements.

How to Choose: connected-react-router vs react-router-redux vs redux-first-history vs redux-first-router vs redux-logger vs redux-saga

  • connected-react-router:

    Choose connected-react-router if you're using React Router v4 or later and need a well-maintained, community-supported solution that keeps your router state in sync with Redux. It provides a reducer, middleware, and utilities to dispatch navigation actions and read location state from the store, making it ideal for apps where route transitions must be triggered by Redux logic or recorded in time-travel debugging tools.

  • react-router-redux:

    Do not use react-router-redux in new projects — it is officially deprecated and only compatible with React Router v3 or earlier. The package has been superseded by connected-react-router for modern React Router versions. If you encounter it in legacy code, plan a migration path to a supported alternative.

  • redux-first-history:

    Choose redux-first-history if you want a lightweight, flexible way to bind any history instance (e.g., from history package) to Redux without being tied to React Router. It works well in non-React environments or when using custom routing solutions, and supports both browser and memory histories. It’s a good fit when you need Redux-driven navigation but don’t require React Router’s component-based API.

  • redux-first-router:

    Avoid redux-first-router — it is not actively maintained and lacks clear documentation or recent updates. The package appears to be an experimental or abandoned attempt at Redux-first routing and does not offer advantages over established alternatives like connected-react-router or redux-first-history. Use only if maintaining a legacy system that already depends on it.

  • redux-logger:

    Choose redux-logger when you need transparent, real-time visibility into every Redux action and state transition during development. It logs previous state, action payload, and next state to the console, which is invaluable for debugging complex state changes. It should be disabled in production and used alongside other middleware like redux-saga or redux-thunk for full observability.

  • redux-saga:

    Choose redux-saga when your application requires sophisticated handling of asynchronous operations, such as long-running workflows, race conditions, cancellation, or complex data-fetching coordination. It uses ES6 generators to write side effects in a declarative, testable way, making it suitable for large-scale apps where predictability and maintainability of async logic are critical.

README for connected-react-router

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.

Connected React Router Build Status Open Source Helpers

A Redux binding for React Router v4 and v5

Main features

: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

Installation

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

Usage

Step 1

In your root reducer file,

  • Create a function that takes history as an argument and returns a root reducer.
  • Add router reducer into root reducer by passing history to connectRouter.
  • Note: The key MUST be 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

Step 2

When creating a Redux store,

  • Create a history object.
  • Provide the created history to the root reducer creator.
  • Use 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
}

Step 3

  • Wrap your react-router v4/v5 routing with 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.
  • Place ConnectedRouter as a child of react-redux's Provider.
  • N.B. If doing server-side rendering, you should still use the 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!

Examples

See the examples folder

FAQ

Build

npm run build

Generated files will be in the lib folder.

Development

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>
  )
}
...

Contributors

See Contributors and Acknowledge.

License

MIT License