DEV Community

Waqasabi
Waqasabi

Posted on • Updated on

NextJs Redux Server-Side Rendered App with Next.js, React and Redux

There are many known benefits to pre-rendering your web application or rendering your web app on the server-side, some of which include better SEO, faster-load times, a better user experience for users with poor connections and many more.

This post will guide you towards getting quickly started with Next and using it to develop a React-Redux web application.

Note: This post is outdated and was written for Next.js versions 9.2 and lower. Please refer to next-redux-wrapper to see how to create an updated version Next.js-redux web app.

Prerequisites for this post:

  • Understanding of Basic React Concepts
  • Understanding of Basic Redux Concepts

For the purpose of this post, after going through all the concepts, we will be creating a simple counter app with our server-side rendered app.

Getting Started with Next.JS

Next.js is a React-Framework which makes it really easy to develop react server-side rendered apps. It also provides additional features but in this post, we will only go over rendering applications server-side with Next.js.

I highly recommend going over the docs. This part goes over the basic principles of next and there is a lot of overlap with the documentation. I recommend going through the documentation and then proceeding to the next part of this article. Nevertheless, if the documentation are not sufficient, then you can keep reading!

If you already have Next.js installed and know the basics, you can skip to the following

To get started, we first create a project directory:

mkdir hello-next
Enter fullscreen mode Exit fullscreen mode

We then initialize the project with npm:

cd hello-next
npm init -y
Enter fullscreen mode Exit fullscreen mode

We then need to install next, react & react-dom, these are necessary dependencies for next:

npm install --save react react-dom next
Enter fullscreen mode Exit fullscreen mode

We then need to create a 'pages' directory within our project directory. Any React files in this directory by default are mapped to the url routes based on the file-name for our server-side rendered app:

mkdir pages
Enter fullscreen mode Exit fullscreen mode

A file named index.jsx will be mapped to the root url i.e. localhost:3000/.
Similarily a file named login.jsx will be mapped to localhost:3000/login
This feature is enabled by default and for our use-case, this is sufficient.

To get started with next, we need to edit our package.json in our project directory and replace the scripts with the following:

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}
Enter fullscreen mode Exit fullscreen mode

After doing so, everything is ready. You can now run this command in the project directory:

npm run dev
Enter fullscreen mode Exit fullscreen mode

After a few seconds, the development server should be up and running and visiting localhost:3000 will yield "404 | Page Not Found". This is because our pages directory does not have an "index.jsx" file. We can proceed to create it:

touch pages/index.jsx
Enter fullscreen mode Exit fullscreen mode

We can then create a simple Hello-World index page:

import React from 'react';

class App extends React.Component {
    render() {
        return (
            <h1> Hello World! </h1>
        );
    }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here we create a React Component that renders "Hello World" and visiting the root path will show the result.

Next only recognizes the default imports in the React files in the pages directory, and will only render the default Component when browsed to the URL path.

Creating a Simple Counter App (Without Redux)

To create a simple counter app, we can do the following:

import React from 'react';

class App extends React.Component {

    constructor(props) {
        super(props);

        //Initialise state
        this.state = {
            counter: 0
        };
    }

    //Updates the counter in state by +1
    incrementCounter = () => {
        this.setState(prevState => {
            this.setState({
                counter: prevState.counter + 1.
            });
        });
    };

    //Updates the counter in state by  -1
    decrementCounter = () => {
        this.setState(prevState => {
            this.setState({
                counter: prevState.counter - 1.
            });
        });
    };

    render() {
        return (
            <div>
                <button onClick={this.incrementCounter}>Increment</button>
                <button onClick={this.decrementCounter}>Decrement</button>
                <h1>{this.state.counter}</h1>
            </div>
        );
    }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Doing so will show this result:

Result Image

Clicking on the appropriate buttons increment and decrements.

As you can see, next makes use of React and therefore working with next is simple just working with React, the only difference is that next automatically (Behind-the-scenes) renders the application serverside.

Understanding Server-Side Rendering with Redux

Similar to how working with Next is basically just working with React. Redux web apps behave in the same way. Everything works similar to how it would work if the app was rendered on the client-side. The only challenge working with Redux is the initial Redux setup with server-side rendering and this is exactly what the following part covers.

The official documentation, Redux provides a good explanation of how server-side rendering is expected to work with Redux. The explanation states that:

When using Redux with server rendering, we must also send the state of our app along in our response, so the client can use it as the initial state. This is important because, if we preload any data before generating the HTML, we want the client to also have access to this data. Otherwise, the markup generated on the client won't match the server markup, and the client would have to load the data again.

To send the data down to the client, we need to:

  • create a fresh, new Redux store instance on every request;
  • optionally dispatch some actions;
  • pull the state out of store;
  • and then pass the state along to the client.

On the client-side, a new Redux store will be created and initialized with the state provided by the server. Redux's only job on the server-side is to provide the initial state of our app.

This might seem confusing but the important part is:

  1. Initialize and create a new redux store for new user request
  2. (Optional) populate the store with information, for example, you could make use of the user-cookies in the request to identify the user and populate the store with the user information.
  3. Send the redux state to the client
  4. The client then uses the received state to initialize the client-side redux store.

The next part will cover how we can achieve this.

Setting-up Redux

To get started with Redux, we will create a basic Redux app where we keep track of the counter in our state.

We need to first install redux and react-redux:

npm install --save redux react-redux
Enter fullscreen mode Exit fullscreen mode

This is how our project structure will look like:

hello-next
    +- .next
    +- node_modules
    +- pages
    +- redux
        +- actions
        +- reducers
Enter fullscreen mode Exit fullscreen mode

To do so we can do the following:

mkdir redux redux/actions redux/reducers
Enter fullscreen mode Exit fullscreen mode

We will now create a counterReducer, which will keep track of our counter state. We can place this in the reducers folder:

touch redux/reducers/counterReducer.js
Enter fullscreen mode Exit fullscreen mode

This is how the counterReducer file will look like:

const counterReducer = (state = {value: 0}, action) => {
    return {...state};
};

export default counterReducer;
Enter fullscreen mode Exit fullscreen mode

This will create an initial state with the counter value being set to 0

Right now our counterReducer does nothing. We can proceed to creating actions:

touch redux/actions/counterActions.js
Enter fullscreen mode Exit fullscreen mode

We will just specify two actions - Increment and Decrement:

//Action Types
export const INCREMENT_COUNTER = "INCREMENT_COUNTER";
export const DECREMENT_COUNTER = "DECREMENT_COUNTER";


//Action Creator
export const incrementCounter = () => ({
   type: INCREMENT_COUNTER
});

export const decrementCounter = () => ({
    type: DECREMENT_COUNTER
});

Enter fullscreen mode Exit fullscreen mode

We can now modify our reducer to include these actions:

import {DECREMENT_COUNTER, INCREMENT_COUNTER} from '../actions/counterActions';

const counterReducer = (state = {value: 0}, action) => {
    switch (action.type) {
        case INCREMENT_COUNTER:
            return {...state, value: state.value + 1};
        case DECREMENT_COUNTER:
            return {...state, value: state.value - 1};
        default:
            return {...state};
    }
};

export default counterReducer;
Enter fullscreen mode Exit fullscreen mode

This will either increment or decrement our counter when INCREMENT_COUNTER or DECREMENT_COUNTER actions are dispatched.

We can now proceed to creating the root reducer, which will be responsible for combining all our reducers. In our case we only have 1 reducer "counterReducer", however for common practice we will proceeding to combining reducers.

Create the rootReducer file:

touch redux/reducers/rootReducer.js
Enter fullscreen mode Exit fullscreen mode

This is how our rootReducer file will look like:

import counterReducer from './counterReducer';
import {combineReducers} from 'redux';

const rootReducer = combineReducers({
    counter: counterReducer
});

export default rootReducer;
Enter fullscreen mode Exit fullscreen mode

This combines all our reducers into one rootReducer which we can use to initalise our redux store.

We can now proceed to creating our redux store:

touch redux/store.js
Enter fullscreen mode Exit fullscreen mode
import {createStore} from 'redux';
import rootReducer from './reducers/rootReducer';

const store = createStore(rootReducer);

export default store;
Enter fullscreen mode Exit fullscreen mode

Now that we have our redux logic setup, we can link our application with redux, using react-redux. However to do this, we need to create a special file named "_app.jsx" located in our pages directory:

touch pages/_app.jsx
Enter fullscreen mode Exit fullscreen mode

next uses the App component to initialize pages. We created the "_app.jsx" file to override the default App Component. To begin with, our new App Component needs to extend the default App component, so that next can still use it to initalise pages.

We can import the default App Component from "next/app" and create our own App component:

import App from 'next/app';

class MyApp extends App {


}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

However, at this moment we are doing nothing. Similar to how Redux is connected to Client-side react apps, we can connect our server-side rendered application here.

We use "Provider" provided by react-redux and connect our store:

import App from 'next/app';
import {Provider} from 'react-redux';
import React from 'react';

class MyApp extends App {

    render() {
        return (
            <Provider store={}>

            </Provider>
        );
    }

}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

But what do we put as the argument for store inside the Provider Component? To finish the setup we must use a static function getInitialProps. This function according to the next docs is responsible for:

Next.js comes with getInitialProps, which is an async function that can be added to any page as a static method.

getInitialPropsallows the page to wait for data before rendering starts.

Using getInitialProps will make the page opt-in to on-demand server-side rendering.

Every page that has getInitialProps will be server-side rendered. If you do not include this method then the file will be rendered to static HTML at next build time. Including this function will allow this page to render on the server, and everything inside that function will be executed before sending the page to the client. This is helpful in cases where our page needs data that needs to be fetched. Returning anything from this function will allow that information to be sent to the client. The client can access the information returned from this function using the props of the React component.

This is also where we can choose to optionally populate our redux state before sending it to the client, adding this function to our "_app.jsx" looks like:

import App from 'next/app';
import {Provider} from 'react-redux';
import React from 'react';
import {INCREMENT_COUNTER} from '../redux/actions/counterActions';

class MyApp extends App {

    static async getInitialProps({Component, ctx}) {
        const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};

        //Anything returned here can be access by the client
        return {pageProps: pageProps};
    }

    render() {
        //Information that was returned  from 'getInitialProps' are stored in the props i.e. pageProps
        const {Component, pageProps} = this.props;

        return (
            <Provider store={}>
                <Component {...pageProps}/>
            </Provider>
        );
    }

}

export default MyApp;

Enter fullscreen mode Exit fullscreen mode

ctx is a getInitialProps parameter referring to Context. You can read more about it here

Using getInitialProps in _app.jsx has a different interface. When using it on normal pages, getInitialProps only has 1 parameter ctx. However in our case, since we are overriding the default App Component, we have access to the default App Component. We need to make sure if the default App component makes use of getInitialProps then we need to send whatever that function returned to the client.

Moving on, to pass the store to the client, we need to wrap the original component with React-Redux's Provider. To make all of this work, we need to install one last library: next-redux-wrapper

npm install --save next-redux-wrapper
Enter fullscreen mode Exit fullscreen mode

Next-redux-wrapper will enable us to create a store at every new request and it will pass it to MyApp (Our App Implementation) as props.

We need to make use of Next-redux-wrapper's withRedux wrapper and wrap our App component with it.

The withRedux function accepts makeStore as first argument. The makeStore function will receive initial state and should return a new instance of Redux store each time when called, no memoization needed here, it is automatically done inside the wrapper.

After connecting with next-redux-wrapper:

import App from 'next/app';
import {Provider} from 'react-redux';
import React from 'react';
import withRedux from "next-redux-wrapper";
import store from '../redux/store';

class MyApp extends App {

    static async getInitialProps({Component, ctx}) {
        const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};

        //Anything returned here can be accessed by the client
        return {pageProps: pageProps};
    }

    render() {
        //pageProps that were returned  from 'getInitialProps' are stored in the props i.e. pageprops
        const {Component, pageProps, store} = this.props;

        return (
            <Provider store={store}>
                <Component {...pageProps}/>
            </Provider>
        );
    }
}

//makeStore function that returns a new store for every request
const makeStore = () => store;

//withRedux wrapper that passes the store to the App Component
export default withRedux(makeStore)(MyApp);


Enter fullscreen mode Exit fullscreen mode

After the following the changes, our app is ready! We can now use Redux as we normally would. Changing our index.jsx to incorporate redux.

import React from 'react';
import {connect} from 'react-redux';
import {decrementCounter, incrementCounter} from '../redux/actions/counterActions';

class App extends React.Component {

        static getInitialProps({store}) {}

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>
                <button onClick={this.props.incrementCounter}>Increment</button>
                <button onClick={this.props.decrementCounter}>Decrement</button>
                <h1>{this.props.counter}</h1>
            </div>
        );
    }
}

const mapStateToProps = state => ({
    counter: state.counter.value
});

const mapDispatchToProps = {
    incrementCounter: incrementCounter,
    decrementCounter: decrementCounter,
};

export default connect(mapStateToProps, mapDispatchToProps)(App);

Enter fullscreen mode Exit fullscreen mode

We use React-Redux's connect to connect the Redux state to our page, and we use mapStateToProps and mapDispatchToProps to connect our state and actionCreators to our page.

After running the page, our React-Redux app works as expected! Clicking on the buttons Increments and/or Decrements!

Congratulations, you now know the basics of how to create a Server-side Rendered React-Redux Application using next.js

One thing to note is that at the moment Redux only works as a Single Page Application, what this means is that Client-side routing is the only way for the Redux store to be transferred between pages.

This means that if the user navigates to a different URL (i.e. Server-side routing) then the server will treat it as a new client and serve an empty redux state. To learn how to persist the redux-state so that the counter values stays the same for each refresh refer to next-redux-wrapper guide. However please ensure that you update your Next.js version and the next-redux-wrapper version first and follow the updated guide.

The code for this project can be found on Github

This is the end of this post! This was my first post and I hope you enjoyed reading it! Any feedback is appreciated!

If you would like to read more yourself refer to next-redux-wrapper repository

Top comments (13)

Collapse
 
markerikson profile image
Mark Erikson

Please check out our new official Redux Toolkit package. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once:

redux-toolkit.js.org

Collapse
 
aidosmen profile image
aidosmen

Can you plesae show the example how to dispatch actions in getStaticProps and how to get data from Redux into the getStaticPaths , in case if you use dynamic route [id].jsx

Collapse
 
daniilgri profile image
Danil Grishaev

/!\ You are using legacy implementaion. Please update your code: use createWrapper() and wrapper.withRedux().

Collapse
 
metin1 profile image
metin1

I had the same problem. I find a solution like this: "If you use Thunks middleware you don't need to use next-redux-wrapper." I removed it and my problem was solved.

Collapse
 
pushpit07 profile image
Pushpit

How does that work @metin1? Can you please explain or give an example? I'm facing the same issue

Collapse
 
rxmvinv profile image
rxmvinv

Thanks. But don't forget to keep versions of packages from repository. In latest versions store isn't taken from "this.props" in MyApp component in _app.jsx

Collapse
 
tmangosteen65 profile image
tmj65

Can't wait for part 2

Collapse
 
tsuki1819 profile image
Yare la lesbiana 🤠

Excellent content !. Waiting for the next part <3

Collapse
 
mahdidavoodi7 profile image
Mahdi Davoodi

Could you please tell me how to connect this app to the express server ??

Collapse
 
lukfcsl profile image
Lucas Farias

There is no express server with next.js

Collapse
 
5ervant profile image
Mark Anthony B. Dungo • Edited

Is it really good to use Redux on a Next.js app or the other middleware, or not?

Collapse
 
infamousgodhand profile image
Huang

Great write up, has got me started in the right direction. Thank you

Collapse
 
rbueno profile image
Rafael Bueno

Hi amazing content. Pleasae the second party regarding localstorage with next js