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 URLquery
– query string section of URL parsed as an objectasPath
–String
of the actual path (including the query) shows in the browserreq
– HTTP request object (server only)res
– HTTP response object (server only)jsonPageRes
– Fetch 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 passtrue
as the second argument tourl.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.