Redux Saga in Next.js by Example

 by Robin Wieruch
 - Edit this Post

This article will show you the basic steps to use Redux Saga as side-effect library for asynchronous actions in NextJs. I am no expert in server-side rendering, so maybe you as a reader have more insights that you want to share in the comments.

Recently I used Next.js for a server-rendered React.js application. It comes with its open source create-next-app project, which can be seen as pendant to create-react-app by Facebook, but only as a boilerplate for server-rendered React applications.

There are a few things that are different in a server-side rendered React application. I guess the perfect place to learn about those is Learning Next.js. But basically in NextJs you have pages in a pages/ directory that act as routes in your ReactJs + NextJs application.

  • pages/home -> www.yourdomain/home
  • pages/about -> www.yourdomain/about

However, after you have learned the basic steps in Next, you might want to introduce more aspects to your React + Next application. In my case, it were the libraries Redux, for state management beyond local state, and Redux Saga, as side-effect library for asynchronous actions.

Redux in Next.js

There are the few things that work differently in a server-rendered React application. The crux of the matter is that the Redux store is a singleton in your client-side application, but on the server-side the Redux store is a new instance with every request. That's what makes the Redux store setup slightly different in a NextJs application.

Fortunately, a library took already care of these circumstances: next-redux-wrapper. So how to use it?

In react-redux you were used to use a Provider component at the top of your component tree. It injects the Redux store (via the React context) as dependency to all its child components.

<Provider store={store}>
<MyRootComponent />
</Provider>

But in a server-side rendered React application, the store instance is not a singleton. Thus, in the next-redux-wrapper, you provide the functionality to initialize the store instead of the store instance itself. That way, the server has the chance to create the Redux store every time anew with each incoming request.

import { createStore } from 'redux';
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
};
const initializeStore = initialState => {
return createStore(reducer, initialState);
};
// exports the functionality to initialize the store
// rather than exporting the store instance
export default initializeStore;

In your pages/ directory, you export entry points, called pages, to your Next.js application as plain components.

const HomePage = () =>
<div>
That's the home page.
</div>
export default HomePage;

Now, instead of using the Provider component, you can use the withRedux higher order component to inject the store initialization functionality. If you are not familiar with React's higher order components, you can read .

import withRedux from 'next-redux-wrapper';
import initializeStore from './path/to/store';
const HomePage = () =>
<div>
That's the home page.
</div>
export default withRedux(initializeStore)(HomePage);

Basically, in a server-side rendered React application with Next.js, you can exchange the Provider component from react-redux with withRedux from next-redux-wrapper. You can use it for every entry point in your pages/ directory.

In your child components, you can still use the connect higher order component from react-redux to make your Redux store accessible with mapStateToProps and mapDispatchToProps. It works the same as before.

Redux Saga + Next.js

Last but not least, I had the requirement to use Redux Saga for asynchronous Redux actions in my Next.js application. The basic Redux Saga middleware lookup when creating a Redux store looks similar to this:

import createSagaMiddleware from 'redux-saga';
import rootSaga from 'path/to/combined/sagas';
import rootReducer from 'path/to/combined/reducers';
const saga = createSagaMiddleware();
const store = createStore(
rootReducer,
undefined,
applyMiddleware(saga)
);
saga.run(rootSaga);
export default store;

However, I ran into the issue that the Saga middleware ran before the store got initialized.

Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware

Therefore, the solution was that the Saga can live in the Redux store initialization too.

import createSagaMiddleware from 'redux-saga';
import rootSaga from 'path/to/combined/sagas';
import rootReducer from 'path/to/combined/reducers';
const saga = createSagaMiddleware();
const initializeStore = initialState => {
const store = createStore(
rootReducer,
initialState,
applyMiddleware(saga)
);
saga.run(rootSaga);
return store;
};
export default initializeStore;

That's it. The Saga middleware runs only when the Redux store initialized. Since I have found no other solutions around this topic, I am keen to hear your feedback on this article.

Keep reading about 

In this tutorial, I want to show you how to use React Table Library to retrieve server-side data featuring examples of server-side pagination, server-side search and filter, and server-side sort…

I delved deep into exploring Server Actions in Next.js 14 using Next's App Router and React Server Components (RSC). This comprehensive tutorial shows how to use Server Actions to create, read, update…

The Road to React

Learn React by building real world applications. No setup configuration. No tooling. Plain React in 200+ pages of learning material. Learn React like 50.000+ readers.

Get it on Amazon.