This tutorial is out of date and no longer maintained.
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.
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:
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?
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:
./static
/ is mapped to /static/
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.
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:
- npm init
Install Next.js:
- 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:
// 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:
- 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:
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.
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>
);
}
}
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.
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:
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 notgetInitialProps
because whengetInitialProps
is called, the browser is not prepared sosessionStorage
is not known. For this reason, we need to wait for the browser and a good way to catch that is withcomponentDidMount
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>
);
}
}
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.
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:
…but with the custom error page, you get:
Next is always production-ready at all times. Let’s deploy to now to see how simple deploying is. First, install now
:
- 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:
- npm run build
Start:
- npm start
You can just deploy by running now
:
- now
In this article, you learned how to build server-rendered React apps with Next.js.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
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!