Tutorial

React Universal with Next.js: Server-side React

Draft updated on Invalid Date
author

Chris Nwamba

React Universal with Next.js: Server-side React

This tutorial is out of date and no longer maintained.

Introduction

The term “universal” is a community-coined term for building web apps that render happily on a server. You might be familiar with “isomorphic” as well but the goal of this article is not to debate names; we are going to learn how to build server-rendered React apps with Next.js.

We’ve talked about building React server-side before. Today we’ll discuss the topic more since it’s an important one.

Why Build Universal Apps

React apps implement the virtual DOM ideology which is an abstraction of the real/original DOM. This abstraction is very useful to app performance because we can take a portion of the DOM, bind whatever data we need to bind, and insert it back to the original DOM tree. This is in no way standardized and is just a concept that front-end frameworks utilize to make a better user experience a true story.

Just as every great thing comes with a price, Virtual DOM poses a problem. The original DOM which was received based on some information provided by the server has been lost. You might be wondering why that matters – it does and we will see how.

Search Engines do not care about how your app is architected or whatever ideology was used so as to adjust and fetch the right content. Their bots are not as smart as using your apps as a real user would. All they care about is that once they send their spiders to crawl and index your site, whatever the server provides on the first request is what gets indexed. That is bad news because at that time your app lacks the actual content on the server. This is what the spider takes home from your website:

React Server-side Preview

Preview

React Server-side Preview

Source

Things even get worst when you try to share your app on a social media platform like Facebook or Twitter. You are going to end up with an unexpected behavior because your actual content is not loaded on the server, it’s just the entry point (probably the content of some index.html) that is.

How do we tackle these problems?

Go Universal with Next.js

Universal apps are architected in such a manner that your app renders both on the client and the server. In React’s case, the virtual DOM is eventually dropped on the server as well as using some mechanisms that might give you a headache if you don’t choose the right tool.

I have worked with few solutions but Next.js is very promising. Next is inspired by the 7 Principles of Rich Applications. The idea is to have a good experience while using a web app as well as building it. The experience should feel natural.

Next offers more out of the box:

  • No configuration or additional setup
  • Easy component and global style with Glamor
  • Automatic transpilation and bundling (with webpack and babel)
  • Hot code reloading
  • Static file serving. ./static/ is mapped to /static/
  • Route prefetching. Coming soon

DEMO: Barclays Premier League Table

Let’s do something fun with Next.js together. We will use the Football Data API to build a simple small app that shows the Barclays Premier League ranking table. If that sounds like fun to you, then let’s get started.

Prerequisites

Next.js is just an npm package and all you need to do is install locally and start building your app.

Start a new project:

  1. npm init

Install Next.js:

  1. npm install next --save

Once the installation is completed, you can update the start script to run next:

"scripts": {
   "start": "next"
 },

The Next installation installs React as well so no need to do that again. All you need to do now to put a server-rendered web page out there is to create a pages directory in the root of the app and add an index.js file:

./pages/index.js
// Import React
import React from 'react'

// Export an anonymous arrow function
// which returns the template
export default () => (
  <h1>This is just so easy!</h1>
)

Now run the following command to see your app running at localhost:3000:

Start your app:

  1. npm start

Preview

Source

I bet this was easier than you ever expected. You have a running app in about 5 minutes that is server-rendered. We are making history!

We can add a head section in the page so as to define global styles and set meta details:

./pages/index.js
import React from 'react'
// Import the Head Component
import Head from 'next/head'

export default () => (
  <div>
    <Head>
        <title>League Table</title>
        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
        <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
    </Head>
    <h1>This is just so easy!</h1>
  </div>
)

The Head component which is provided by Next is used to attach head to the DOM of a page. We just wrap the supposed head DOM content in Head Next component.

Ajax Requests

Next provides a getInitialProps which can be used with async (optional) which assists in performing async operations. You can use the await keyword to handle operations that are deferred. See the following example:

import React from 'react'
import Head from 'next/head'
import axios from 'axios';

export default class extends React.Component {
    // Async operation with getInitialProps
    static async getInitialProps () {
        // res is assigned the response once the axios
        // async get is completed
        const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable');
        // Return properties
        return {data: res.data}
      }
 }

We are using the axios library to carry out HTTP requests. The requests are asynchronous so we need a way to catch the response in the future when it is available. With async...await, we can actually handle the async request without having to use callbacks or chain promises.

We pass the value to props by returning an object which properties can be accessed from the props like this.props.data:

import React from 'react'
import Head from 'next/head'
import axios from 'axios';

export default class extends React.Component {
  static async getInitialProps () {
    const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable');
    return {data: res.data}
  }
  render () {
    return (
      <div>
        <Head>
            <title>League Table</title>
            <meta name="viewport" content="initial-scale=1.0, width=device-width" />
            <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
        </Head>
        <div className="pure-g">
            <div className="pure-u-1-3"></div>
            <div className="pure-u-1-3">
              <h1>Barclays Premier League</h1>
              <table className="pure-table">
                <thead>
                  <tr>
                    <th>Position</th>
                    <th>Team</th>
                    <th>P</th>
                    <th>GL</th>
                    <th>W</th>
                    <th>D</th>
                    <th>L</th>
                  </tr>
                </thead>
                <tbody>
                {this.props.data.standing.map((standing, i) => {
                  const oddOrNot = i % 2 == 1 ? "pure-table-odd" : "";
                  return (
                      <tr key={i} className={oddOrNot}>
                        <td>{standing.position}</td>
                        <td><img className="pure-img logo" src={standing.crestURI}/></td>
                        <td>{standing.points}</td>
                        <td>{standing.goals}</td>
                        <td>{standing.wins}</td>
                        <td>{standing.draws}</td>
                        <td>{standing.losses}</td>
                      </tr>
                    );
                })}
                </tbody>
              </table>
            </div>
            <div className="pure-u-1-3"></div>
        </div>
      </div>
    );
  }
}

React Universal App Progress

We can now bind the data to the rendered template by iterating over the standing property on the data and printing each of the standings. The class names are as a result of the Pure CSS style included in the head which is a very simple CSS library to get you started.

Routing

You might not be aware but we already have routes in our application. Next does not require any additional configuration to set up routing. You just keep adding routes as pages to the pages directory. Let’s create another route to show more details about a team:

./pages/details.js
import React from 'react'
export default () => (
  <p>Coming soon. . .!</p>
)

You can navigate from one route to another using the Link component from Next:

// ./pages/details.js
import React from 'react'

// Import Link from next
import Link from 'next/link'

export default () => (
  <div>
      <p>Coming soon. . .!</p>
      <Link href="/">Go Home</Link>
  </div>
)

It’s time to update the details page to display more information about a given team. The team’s position will be passed as a query parameter, id, to the page. The id will be used to then filter the team’s information:

import React from 'react'
import Head from 'next/head'
import Link from 'next/link'
import axios from 'axios';

export default class extends React.Component {
    static async getInitialProps ({query}) {
        // Get id from query
        const id = query.id;
        if(!process.browser) {
            // Still on the server so make a request
            const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable')
            return {
                data: res.data,
                // Filter and return data based on query
                standing: res.data.standing.filter(s => s.position == id)
            }
        } else {
            // Not on the server just navigating so use
            // the cache
            const bplData = JSON.parse(sessionStorage.getItem('bpl'));
            // Filter and return data based on query
            return {standing: bplData.standing.filter(s => s.position == id)}
        }
    }

    componentDidMount () {
        // Cache data in localStorage if
        // not already cached
        if(!sessionStorage.getItem('bpl')) sessionStorage.setItem('bpl', JSON.stringify(this.props.data))
    }

    // . . . render method truncated
 }

This page displays dynamic content based on the query parameter. We receive the query from the getInitialProps and then use the id value to filter the data array from a given team based on their position on the table.

The most interesting aspect of the above logic is that we are not requesting data more than once in the app’s lifecycle. Once the server renders, we fetch the data and cache it with sessionStorage. Subsequent navigation will be based on the cached data. sessionStorage is preferred to localStorage because the data will not persist once the current window exits.

The storage is done using componentDidMount and not getInitialProps because when getInitialProps is called, the browser is not prepared so sessionStorage is not known. For this reason, we need to wait for the browser and a good way to catch that is with componentDidMount React lifecycle method. getInitialProps is said to be isomorphic.

Let’s now render to the browser:

// ... truncated

export default class extends React.Component {
    // ... truncated
    render() {

        const detailStyle = {
            ul: {
                marginTop: '100px'
            }
        }

        return  (
             <div>
                <Head>
                    <title>League Table</title>
                    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
                    <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
                </Head>

                <div className="pure-g">
                    <div className="pure-u-8-24"></div>
                    <div className="pure-u-4-24">
                        <h2>{this.props.standing[0].teamName}</h2>
                        <img src={this.props.standing[0].crestURI} className="pure-img"/>
                        <h3>Points: {this.props.standing[0].points}</h3>
                    </div>
                    <div className="pure-u-12-24">
                        <ul style={detailStyle.ul}>
                            <li><strong>Goals</strong>: {this.props.standing[0].goals}</li>
                            <li><strong>Wins</strong>: {this.props.standing[0].wins}</li>
                            <li><strong>Losses</strong>: {this.props.standing[0].losses}</li>
                            <li><strong>Draws</strong>: {this.props.standing[0].draws}</li>
                            <li><strong>Goals Against</strong>: {this.props.standing[0].goalsAgainst}</li>
                            <li><strong>Goal Difference</strong>: {this.props.standing[0].goalDifference}</li>
                            <li><strong>Played</strong>: {this.props.standing[0].playedGames}</li>
                        </ul>
                        <Link href="/">Home</Link>
                    </div>
                </div>
             </div>
            )
    }
}

Our index page does not implement this performance-related feature in details page. We can update the component accordingly:

// ... imports truncated

export default class extends React.Component {
  static async getInitialProps () {
    if(!process.browser) {
      const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable')
      return {data: res.data}
    } else {
      return {data: JSON.parse(sessionStorage.getItem('bpl'))}
    }
  }

  componentDidMount () {
    if(!sessionStorage.getItem('bpl')) sessionStorage.setItem('bpl', JSON.stringify(this.props.data))
  }
  // ... render method truncated
}

The index template should also be updated to include links that point to each team’s details page:

// ... some imports truncated
import Link from 'next/link'

export default class extends React.Component {
 // ... truncated
  render () {
    const logoStyle = {
      width: '30px'
    }

    return (
      <div>
        <Head>
            <title>League Table</title>
            <meta name="viewport" content="initial-scale=1.0, width=device-width" />
            <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
        </Head>
        <div className="pure-g">
            <div className="pure-u-1-3"></div>
            <div className="pure-u-1-3">
              <h1>Barclays Premier League</h1>

              <table className="pure-table">
                <thead>
                  <tr>
                    <th>Position</th>
                    <th>Team</th>
                    <th>P</th>
                    <th>GL</th>
                    <th>W</th>
                    <th>D</th>
                    <th>L</th>
                    <th></th>
                  </tr>
                </thead>
                <tbody>
                {this.props.data.standing.map((standing, i) => {
                  const oddOrNot = i % 2 == 1 ? "pure-table-odd" : "";
                  return (
                      <tr key={i} className={oddOrNot}>
                        <td>{standing.position}</td>
                        <td><img className="pure-img logo" src={standing.crestURI} style={logoStyle}/></td>
                        <td>{standing.points}</td>
                        <td>{standing.goals}</td>
                        <td>{standing.wins}</td>
                        <td>{standing.draws}</td>
                        <td>{standing.losses}</td>
                        <td><Link href={`/details?id=${standing.position}`}>More...</Link></td>
                      </tr>
                    );
                })}
                </tbody>
              </table>
            </div>
            <div className="pure-u-1-3"></div>
        </div>
      </div>
    );
  }
}

Error Pages

You can define custom error pages by creating an _error.js page to handle 4** and 5** errors. Next already displays errors so if that is fine with you, then no need to create the new error page.

./pages/_error.js
import React from 'react'

export default class Error extends React.Component {
  static getInitialProps ({ res, xhr }) {
    const statusCode = res ? res.statusCode : (xhr ? xhr.status : null)
    return { statusCode }
  }

  render () {
    return (
      <p>{
        this.props.statusCode
        ? `An error ${this.props.statusCode} occurred on server`
        : 'An error occurred on client'
      }</p>
    )
  }
}

This is what a 404 looks like with the default error page:

React Universal 404

…but with the custom error page, you get:

Deploying

Next is always production-ready at all times. Let’s deploy to now to see how simple deploying is. First, install now:

  1. npm install -g now

You need to get an account by downloading the desktop app

Update the scripts in package.json to include a build command for our app:

"scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },

Now run the build and start command to prepare for deploying.

Build:

  1. npm run build

Start:

  1. npm start

You can just deploy by running now:

  1. now

Conclusion

In this article, you learned how to build server-rendered React apps with Next.js.

Additional Resources

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Chris Nwamba

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.