Next.js + Redux
Integrating redux to your server side rendered application in nextjs
NOTE: A updated version of this post is available here.
Integrating redux and nextjs requires some extra steps because you have to create a redux instance on every page, which is not the case in single-page applications. So, we need to create a wrapper that initializes redux store on all the pages.
There is a package called next-redux-wrapper, but for some reason, it wasn't working for me. So, I created a wrapper instead of relying on other packages.
In case, if you don't know about nextjs and redux,
Nextjs is a react framework used to make your websites server-rendered which increases the performance of your site and also has SEO benefits.
Redux is a popular state management library in the react world. If you have a large web application, it makes sense to separate and the application state and view.
First, let's install the required packages, you can use npm or yarn for installation
npm init -y
npm install react react-dom next redux react-redux react-thunk
Optional package
npm install redux-devtools-extension
Configuring Redux in Nextjs
Next.js initializes page using the App component, and we will be overriding it to initialize redux store in _app.js. with-redux-store.js is a higher-order component that will be used in _app.js page.
This wrapper file is from next.js Github repository
// /lib/with-redux-store.js
import React from 'react'
import { initializeStore } from '../store'
const isServer = typeof window === 'undefined'
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
function getOrCreateStore(initialState) {
// Always make a new store if server, otherwise state is shared between requests
if (isServer) {
return initializeStore(initialState)
}
// Create store if unavailable on the client and set it on the window object
if (!window[__NEXT_REDUX_STORE__]) {
window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
}
return window[__NEXT_REDUX_STORE__]
}
export default App => {
return class AppWithRedux extends React.Component {
static async getInitialProps(appContext) {
// Get or Create the store with `undefined` as initialState
// This allows you to set a custom default initialState
const reduxStore = getOrCreateStore()
// Provide the store to getInitialProps of pages
appContext.ctx.reduxStore = reduxStore
let appProps = {}
if (typeof App.getInitialProps === 'function') {
appProps = await App.getInitialProps(appContext)
}
return {
...appProps,
initialReduxState: reduxStore.getState(),
}
}
constructor(props) {
super(props)
this.reduxStore = getOrCreateStore(props.initialReduxState)
}
render() {
return <App {...this.props} reduxStore={this.reduxStore} />
}
}
}
It allows keeping the state when navigating between pages as it wraps MyApp component, which will be available on all the pages with withReduxStore higher-order function.
// \_app.js
import App, { Container } from 'next/app'
import React from 'react'
import withReduxStore from '../lib/with-redux-store'
import { Provider } from 'react-redux'
class MyApp extends App {
render() {
const { Component, pageProps, reduxStore } = this.props
return (
<Container>
<Provider store={reduxStore}>
<Component {...pageProps} />
</Provider>
</Container>
)
}
}
export default withReduxStore(MyApp)
Unlike in a single-page application, we will return the initialize store function instead of the store itself.
// /src/store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "../src/reducers";
export function initializeStore(initialState = {}) {
return createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(thunk))
);
Now all the components can have access to the redux store using connect function from react-redux.
Dispatching actions in getInitialProps
You can dispatch redux actions in getInitialProps via reduxStore object. You can destructure the reduxStore object from the getInitialProps parameters.
// /pages/index.js
import React from 'react'
function index() {
return <h1>Dispatching actions from getInitialProps</h1>
}
index.getInitialProps = ({ reduxStore }) => {
reduxStore.dispatch({ type: 'INCREMENT' })
return {}
}
Source code
Here's the source code if you want to try it out in your machine.
cd next-redux-example
npm install
npm run dev