Next.js + Redux in an easy way

Kirill Konshin
Feb 26, 2017 · 5 min read

This is the second article about Server Side Rendering of React-based stack. The first one React + Router + Redux and Server Side Rendering was more vanilla and custom, this article is more aimed on beginners who don’t want to bother about nuances and wish to have a recognized solution with active community, as painless as it could be.

First, let me quote myself and briefly tell why would anyone ever bother about Server Side Rendering:

At my work I use React as the primary UI framework, adoption started with the service site, a huge web application (hundreds of screens and dozens of flows and wizards) that allows users and admins to configure the system. Successful technologies and approaches tend to eventually propagate more and more to other products of the company, so the React expansion was not a surprise and now React stack is used as a standard approach. Unfortunately, there is no silver bullet, requirements differ between projects, so at some point it became obvious that we no longer can live with just simple client side rendering and should take a look on a server side rendering.

There is a whole new class of challenges in client-side single page apps, but the main issue always was the start up time. Users have to wait while the initial HTML loads, then CSS and JS, then app bootstraps, then it renders, then data is fetched, then it renders again with data. Only at this point the app becomes usable. Server side rendering allows to deliver more stuff up-front. This becomes crucial if we make flows that are embedded into native mobile apps, where we want seamless experience between native and web.

Number one objective is to reach the certain page, obey all the rules defined in routes, then dispatch some actions, then render the page and send it to client. Fairly straightforward.

One of the potential candidates to become the company-wide standard were frameworks Next.js or Electrode, so why not just take it and forget about the pain. Or not? Maybe the pain will get even worse? Next.js comes with benefits, but also it comes with restrictions and opinionated patterns:

  • it does not have custom routes out of the box, existing modules have their own caveats, most popular React Router is not used
  • does not work with old-fashioned CSS, issues with taking third-party components with styles
  • same story with SASS/LESS

It may be good for beginners, but sometimes this lack of flexibility can’t be accepted. Next.js comes with pre-baked server side rendering approach, all you need is to plug your pages into it’s lifecycle and enjoy. You need to carefully analyze all potential tradeoffs before diving into Next.js world.

You can read the full research and comparison if you are interested.

Next.js & Redux

I assume that if you landed here, then you should already know what Next.js and Redux are. For those who don’t: Next.js is a framework for painless React app development, capable of server rendering. Redux is a data framework that implements so called Flux paradigm, which, in turn, is a unidirectional data flow.

Next.js lifecycle

When it renders pages, it takes files located in ./pages directory, grabs default export and uses getInitialProps static method of exported component to inject some props to it. The method can also be asynchronous. Same method is called both on server side and on client side, so the behavior is mostly consistent, the difference is in the amount of arguments that it receives, for example, server has req (which is the NodeJS Request), client doesn’t have it. Both will receive normalized pathname and query.

Here is a minimalistic page component:

export default class Page extends Component {
getInitialProps({pathname, query}) {
return {custom: 'custom'}; // pass some custom props to component
}
render() {
return (
<div>
<div>Prop from getInitialProps {this.props.custom}</div>
</div>
)
}
}
// OR in functional styleconst Page = ({custom}) => (
<div>
<div>Prop from getInitialProps {this.props.custom}</div>
</div>
);
Page.getInitialProps = ({pathname, query}) => ({
custom: 'custom' // pass some custom props to component
});
export default Page;

What we want to do on a server while rendering the React app which also has Redux inside? We’d like to dispatch some actions before we ship the resulting HTML to the client. For this case getInitialProps is the best bet. We can prepare our Redux Store’s state there so that when component will be rendered, the state will have all the right things, so the resulting HTML will have them too.

getInitialProps({store, pathname, query}) {
// component will read it from store's state when rendered
store.dispatch({type: 'FOO', payload: 'foo'});
// pass some custom props to component
return {custom: 'custom'};
}

But in order to do that we need to first prepare the Redux store, both on client and on server, while keeping in mind that on client the store must be a singleton, but on server store has to be created for each request. When store is created we’d like to inject it into getInitialProps along with the rest of Next.js things.

The same store must later be passed to React Redux Provider, so that all it’s children will have access to it.

For this purpose the Higher Order Components work the best. In a few words, it’s a function accepts a Component and returns a wrapped version of it, which is also a Component. Sometime a HOC can accept some arguments and return another function, which in turn will take Component as argument. This wrapper can be applied to all our pages.

Basically, this was the short explanation how next-redux-wrapper package works.

First, in has to be installed:

npm install next-redux-wrapper --save

Next, we need to set things up:

import React, {Component} from "react";
import {createStore} from "redux";
// create a simple reducer
const reducer = (state = {foo: ''}, action) => {
switch (action.type) {
case 'FOO':
return {...state, foo: action.payload};
default:
return state
}
};
// create a store creator
const makeStore = (initialState) => {
return createStore(reducer, initialState);
};
export default makeStore;

Now we are ready to wrap everything with next-redux-wrapper:

import withRedux from "next-redux-wrapper";const Page = ({foo, custom}) => (
<div>
<div>Prop from Redux {foo}</div>
<div>Prop from getInitialProps {custom}</div>
</div>
);
Page.getInitialProps = ({store, isServer, pathname, query}) => {
// component will read from store's state when rendered
store.dispatch({type: 'FOO', payload: 'foo'});
// pass some custom props to component from here
return {custom: 'custom'};
};

Page = withRedux(makeStore, (state) => ({foo: state.foo}))(Page);

export default Page;

Simple, yet, powerful.

You can see the full example in Next.js repo: https://github.com/zeit/next.js/blob/master/examples/with-redux/README.md or check out the main repo’s example: https://github.com/kirill-konshin/next-redux-wrapper/blob/master/pages/index.js.

I have noticed one potential caveat. If you plan to use Next.js’s pages/_document.js and you’d like to dispatch actions from its’ getInitialProps you may run into a nasty race condition issue. Your page will be rendered before document’s actions will be dispatched, so when document will be rendered, you will have inconsistent store state.

The wrapper itself supports this scenario too, because it saves an instance of Store in request for server and in memoized variable for client, but authors of Next.js say it’s a bad choice from lifecycle perspective and you should never do that. Mkay… Instead, you can create your own HOC and dispatch whatever you want from there also from page-level, without even having the document.

D.i.S

Developer/ DJ / Producer / Photographer / Traveller

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store