Building Server-rendered React Apps with NextJS

Building Server-rendered React Apps with NextJS

In this post, I’ll explain what NextJS is, and how you can use it to build a server-rendered React web application and deploy it to production.

Update: This guide has been updated to work with NextJS v9.0 🎊

Background

I recently used NextJS to build Remote Job Lists. I enjoyed making it and I think a large part of that is how easy and fun it was to build with NextJS. It finally makes it easy to write server-rendered React applications.

Why learn NextJS?

For a few years now, web developers have been trying to figure out how to build applications that take advantage of server rendering while having the user experience that client-side routing offers. There have been countless blog posts on this topic.

The folks over at Zeit have tried to solve this problem by building NextJS: a framework for server-rendered React applications.

Here’s how they explain it:

Think about how webapps are created with PHP. You create some files, write PHP code, then simply deploy it. We don’t have to worry about routing much, and the app is rendered on the server by default.

That’s exactly what we do with Next.js. Instead of PHP, we build the app with JavaScript and React.

Recently, I used NextJS to build Remote Job Lists, a web application to help people find remote jobs. Coming from a React/Express background, I was impressed with the simplicity and elegance of NextJS.

If you’re comfortable building applications using Express and React, I think it’s worthwhile to learn Next as it can really simplify the process of building isomorphic web applications.

The Features

NextJS makes it easy to do the following:

  • Server-render your application, with subsequent requests being served via client-side routing.
  • Automatic code splitting for faster page loads
  • Simple client-side routing
  • Webpack-based dev environment which supports Hot Module Replacement(HMR)
  • Able to implement with Express or any other Node.js HTTP server
  • Customizable with your own Babel and Webpack configurations

The Basics: Getting Started with NextJS

I won’t go over the basics too much because the NextJS documentation, along with learnnextjs.com does a good job. They will be kept up-to-date as NextJS improves.

Nonetheless, it’s important to know how to do the simple things. So here’s how to set up NextJS and create a simple site:

Create a sample directory for your site, and install react, react-dom, and next via npm.

mkdir hello-next
cd hello-next
npm init -y
npm install --save react react-dom next

Create a folder called pages, and add a file inside it called index.js.

mkdir pages
cd pages
touch index.js

Update index.js with the following content. This is just a simple React component.

const Index = () => (
  <div>Hello Next.js</div>
)

export default Index

Update package.json dev script to run next:

{
  "scripts": {
    "dev": "next"
  }
}

Now, run npm run dev from your command line to boot up the NextJS server and head over to http://localhost:3000. You should see your React component being rendered.

In a few lines of code, NextJS let us setup an Express server that server-renders a React component. There was no need to configure Webpack or set up routes.

If you go back in and update index.js, you’ll see that your web browser will automatically pull in those changes, thanks to the built-in Hot Module Replacement support.

Building Production Applications with NextJS

Below, I’ve outlined some tips and guides to help when building production apps with NextJS.

Fetching Data

Usually, your page will need to fetch some data from a database or API before rendering. In these circumstances, you can use the static getInitialProps method. More information here.

The fetch() API used below is from the isomorphic-unfetch module. I recommend installing this with NextJS as it allows you to use the same fetch() API on the server and client.

npm install isomorphic-unfetch
import fetch from 'isomorphic-unfetch';

const Page = ({ stars }) =>
  <div>
    Next stars: {stars}
  </div>

Page.getInitialProps = async ({ req }) => {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Page

The getInitialProps method receives a context object with the following properties:

  • pathname – path section of URL
  • query – query string section of URL parsed as an object
  • asPathString of the actual path (including the query) shows in the browser
  • req – HTTP request object (server only)
  • res – HTTP response object (server only)
  • jsonPageResFetch Response object (client only)
  • err – Error object if any error is encountered during the rendering

Fetching Data after component has loaded

If you want to fetch data after a page has loaded, you can just use the standard React idioms.

import fetch from 'isomorphic-unfetch';

class Page extends Component {
    
    constructor(props) {
    super(props);
    this.state = {
            stars: props.stars
    }
  }
    handleRefresh = async(e) => {
    const res = await fetch('https://api.github.com/repos/zeit/next.js')
  		const json = await res.json()
  		this.setState({ stars: json.stargazers_count });
  }
    render() {
        return (
        	<div>
    			Next stars: {this.state.stars}
    			<button onClick={this.handleRefresh}>Refresh</button>
  			</div>
        )
    }
}


Page.getInitialProps = async ({ req }) => {
  const res = await fetch('https://api.github.com/repos/zeit/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Page

Creating a Layout

Normally, you’ll have a custom layout around your application. You can create this layout using standard React components.

For example, create a component called Layout.js:

import { Component } from "react";
import Header from "./Header";
import Footer from "./Footer";
import "../assets/css/app.css";

class Layout extends Component {
    render() {
        return (
            <div className="app">
            	<Header />
                <section>{this.props.children}</section>
                <Footer />
            </div>
        );
    }
}

export default Layout;

Now, you can use this Layout component just like any other React Component. For example, let’s say you want to wrap your home page (pages/index.js) within this Layout:

import { Component } from "react";

import Layout from "../components/Layout";
import Meta from "../components/Meta";

class Job extends Component {

    render() {
        return (
            <Layout>
                <Meta
                    title={Remote Job Lists}
                    description={Some sample description}
                />
                <div>Hello World</div>
            </Layout>
        );
    }
}

In this example, I also create a Meta component that I use to inject some custom data in the of the HTML page. More on this in the section below.

Injecting data into <head>

One common usecase for applications is to modify the contents of <head> depending on the current webpage’s URL. For instance, you may want to modify meta tags or add a custom title.

You can use the next/head module for these use cases. It takes its children and injects it into the document’s <head>.

import Head from 'next/head'

export default () =>
  <div>
    <Head>
      <title>My page title</title>
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <p>Hello world!</p>
  </div>

Note: The contents get cleared upon unmounting the component, so make sure each page completely defines what it needs in, without making assumptions about what other pages added

Adding a Custom Document Wrapper for External Styles

By default, NextJS will not let you configure any properties on tags such as or. These tags are outside the scope of your pages. However, what happens if you want to modify these tags?

In these use cases, you can create a special file called _document.js within your pages/ directory. Below, I’ve shown what a sample _document.js file can look like. It allows me to create some meta tags and provide links to external stylesheets.

import Document, { Head, Main, NextScript } from "next/document";

export default class MyDocument extends Document {
    render() {
        return (
            <html>
                <Head>
                    <meta charset="utf-8" />
                    <meta
                        name="viewport"
                        content="width=device-width, initial-scale=1"
                    />
                    <link
                        href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css"
                        rel="stylesheet"
                    />
                    
                    <link rel="stylesheet" href="/_next/static/style.css" />
                </Head>
                <body>
                    <Main />
                    <NextScript />
                </body>
            </html>
        );
    }
}

Read more about custom documents.

Importing Local CSS Files

A common usecase is to import static CSS files into your React components. For example, something like this:

import { Component } from "react";

// Importing a local CSS file.
import "../assets/css/app.css";

class MyPage extends Component {

}

export default MyPage;

To facilitate this, NextJS needs to know how to load these CSS files. Normally, we would do this through a webpack configuration but there is a simpler way. NextJS allows us to tweak its configuration by creating a file called next.config.js in your project root.

First, install the @zeit/next-css npm module.

npm install --save @zeit/next-css

Then, create a next.config.js file:

// next.config.js
const withCSS = require("@zeit/next-css");
module.exports = withCSS();
Create a custom document (pages/_document.js) and add a link tag pointing to /_next/static/style.css.

// ./pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  render() {
    return (
      <html>
        <Head>
          // This link tag should be present.
          <link rel="stylesheet" href="/_next/static/style.css" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

Read more about serving local CSS files

Using a Custom Express Server

The default behavior of NextJS is to serve whatever is stored inside pages/*.js as its own route. However, as an application grows, you might need to tweak this, or add custom server-side logic. Luckily, NextJS exposes the entire ExpressJS server to us if we need it.

First, open package.json and replace next start with node server.js:

{
  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  }
}

Now, you’ll need to create a file server.js in your project root. The following is a barebones server taken from NextJS’s documentation:

// This file doesn't go through babel or webpack transformation.
// Make sure the syntax and sources this file requires are compatible with the current node version you are running
// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  createServer((req, res) => {
    // Be sure to pass true as the second argument to url.parse.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = parse(req.url, true)
    const { pathname, query } = parsedUrl

    if (pathname === '/a') {
      app.render(req, res, '/b', query)
    } else if (pathname === '/b') {
      app.render(req, res, '/a', query)
    } else {
      handle(req, res, parsedUrl)
    }
  }).listen(3000, err => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})

Now that we have our own server, we have to manage the route handling ourselves and tell NextJS what page to render when a certain URL is hit. The app.render() method is responsible for rendering a given page.

For example, the code below will render the React component at ./pages/mypage.js . We pass in the request object, the response object, and any query parameters that we can retrieve from the URL as well.

app.render(req, res,  '/mypage', query);

Read more about custom server setup.

Deploying to Production

General NextJS deployment instructions can be found here. I had some additional ideas that I picked up on:

Deploying to Heroku

Previously, when deploying to Heroku, I had to add the heroku-postbuild script to my package.json. However, this is no longer needed.

Now, you just have to add the following files:

Procfile:

web: NODE_ENV=production node server.js

Update package.json scripts to include a build script:

"scripts": {
  "build": "npx next build",
  "start": "node server.js"
}

Static Deployment

If you want to deploy NextJS as a static HTML app, follow these steps.

Deploying to GitHub Pages

If you’re deploying a NextJS app to GitHub pages, follow these steps.

Wrapping Up

Hopefully this guide helps you while building your next web application. I really like NextJS and I’m looking forward to seeing the ecosystem around it grow. There are many things that I haven’t covered in this tutorial because there’s no point regurgitating the entire developer documentation, so I encourage you to read it yourself.

If you have any questions, tweet to me @tilomitra.

Up Next:

How do JavaScript Page Lifecycle Events work?

How do JavaScript Page Lifecycle Events work?