Skip to main content

Implementing Redux in React vs NextJS

Implementing Redux in React vs NextJS
Deepak Kamboj
Senior Software Engineer
5 min read
Frontend

In React, Redux is commonly used for global state management, often integrated using middleware like Redux Saga or Thunk. When transitioning to Next.js, Redux works similarly, but you’ll need to consider how Next.js handles server-side rendering (SSR) and static site generation (SSG). This guide compares Redux implementation in React and Next.js and explores how to handle SSR and persistent stores in Next.js.


Redux Store in React

In a typical React app, Redux is integrated using the following steps:

  1. Create a Redux store: This is often done in a store.js or store/index.js file.
  2. Provider Setup: Redux Provider is wrapped around your application in index.js or App.js.
  3. Connecting Components: Components use useSelector and useDispatch hooks to connect to Redux.

Example (React)

// store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './reducers';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga);
export default store;
// index.js (React entry point)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);

Redux Store in NextJS

In Next.js, you will handle Redux slightly differently to accommodate server-side rendering (SSR) and static site generation (SSG).

  1. Create the Redux store: This can be done similarly to React.

  2. Next.js Integration: You'll need to integrate the store using next-redux-wrapper to enable SSR.

  3. Provider Setup: You’ll wrap your _app.js component with Redux Provider using next-redux-wrapper. Example (Next.js)

Example (Next.js)

npm install next-redux-wrapper redux redux-saga
// store.js (Next.js)
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { createWrapper } from 'next-redux-wrapper';
import rootReducer from './reducers';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();

const makeStore = () => {
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
return store;
};

export const wrapper = createWrapper(makeStore);
// _app.js (Next.js entry point)
import { wrapper } from '../store';

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}

export default wrapper.withRedux(MyApp);

Comparing Redux Implementation

1. Types

In both React and Next.js, you define action types the same way. Typically, these are stored in a types.js file.

// types.js
export const FETCH_DATA = 'FETCH_DATA';
export const FETCH_SUCCESS = 'FETCH_SUCCESS';
export const FETCH_ERROR = 'FETCH_ERROR';

2. Actions

In both cases, actions are functions that return a type and payload. In Next.js, actions work identically to React.

// actions.js
import { FETCH_DATA, FETCH_SUCCESS, FETCH_ERROR } from './types';

export const fetchData = () => ({ type: FETCH_DATA });
export const fetchSuccess = (data) => ({ type: FETCH_SUCCESS, payload: data });
export const fetchError = (error) => ({ type: FETCH_ERROR, payload: error });

3. Reducers

Reducers in React and Next.js work the same way. The key difference in Next.js is how the initial state may vary based on SSR and SSG needs.

// reducer.js
import { FETCH_DATA, FETCH_SUCCESS, FETCH_ERROR } from './types';

const initialState = {
data: null,
loading: false,
error: null,
};

export default function rootReducer(state = initialState, action) {
switch (action.type) {
case FETCH_DATA:
return { ...state, loading: true };
case FETCH_SUCCESS:
return { ...state, data: action.payload, loading: false };
case FETCH_ERROR:
return { ...state, error: action.payload, loading: false };
default:
return state;
}
}

4. Sagas

Redux Sagas handle side effects like API calls. In Next.js, the next-redux-wrapper ensures sagas work with SSR.

// sagas.js
import { takeLatest, call, put } from 'redux-saga/effects';
import { FETCH_DATA, fetchSuccess, fetchError } from './actions';
import { fetchApi } from './api';

function* fetchDataSaga() {
try {
const data = yield call(fetchApi);
yield put(fetchSuccess(data));
} catch (error) {
yield put(fetchError(error.message));
}
}

export default function* rootSaga() {
yield takeLatest(FETCH_DATA, fetchDataSaga);
}

5. Persistent Store

In React, you may use libraries like redux-persist to persist the Redux store. In Next.js, persistence can be slightly more complex due to SSR.

React Persistent Store:

npm install redux-persist
// store.js
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // Defaults to localStorage
import rootReducer from './reducers';

const persistConfig = {
key: 'root',
storage,
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = createStore(persistedReducer);
const persistor = persistStore(store);

export { store, persistor };

Next.js Persistent Store:

While Redux persist works similarly, with Next.js SSR, you’ll need to be cautious about when you persist the store to avoid server-side issues.

// store.js
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import { createWrapper } from 'next-redux-wrapper';

const persistConfig = {
key: 'root',
storage,
whitelist: ['someReducer'], // Specify which reducers to persist
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const makeStore = () => {
const store = createStore(persistedReducer);
store.__PERSISTOR = persistStore(store);
return store;
};

export const wrapper = createWrapper(makeStore);

Best Practices

  1. SSR with Redux: When using Redux with Next.js, ensure that you're careful about what data is shared across server and client. Avoid persisting sensitive or large data on the server.

  2. State Hydration: Use Next.js getServerSideProps or getStaticProps to hydrate the store with data when needed.

export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
store.dispatch(fetchData());
await store.sagaTask.toPromise();
return { props: {} };
});
  1. Lazy Loading: Consider splitting up reducers and sagas to avoid a large bundle size. Use combineReducers or lazy loading of reducers where applicable.

Final Thoughts

The main differences between implementing Redux in React and Next.js revolve around SSR and Next.js-specific features. Redux remains consistent in both, but special attention must be paid to handling server-side rendering and hydration in Next.js.

By using next-redux-wrapper, Redux works seamlessly in Next.js, allowing you to manage global state efficiently while benefiting from server-side rendering.