How to get React SEO-friendly by using Next.js

Written by Arnaud Lewis in Engineering on September 10,2018

SEO has become one of the most crucial points on the Internet to get a good ranking on Google and offer visibility to your website. If you have ever wondered how to stick with React for your web application without the lack of SEO and poor latency on the first load, you should take a look at what is coming Next.

The goal for Next.js is to offer a developer experience close to what you have with a simple React application, by gaining all the benefits of a Universal App. It reduces the learning curve and simplifies the adoption from the React community.

This article is meant to demonstrate the concepts of Universal Application and how Next.js tackles it. We provide some detailed code snippets coming from a fully functional example which you can explore.

Setting up your environment

You only need to have Node.js installed on your system to begin building Next.js applications.
You’ll also have to set up the following dependencies in your project:

  • next:
npm install --save next
  • Express:
npm install --save express
  • next-routes:
npm install --save next-routes

These three libraries are all the basics that you need to get started.

Next.js’ default page-based routing

Next.js offer a routing system out of the box. It’s completely page-based which offers a configuration-free routing system.
Paged-based routing means that one component is linked to a url based on the path of your component in the project.

When getting started with Next.js, you have two simple actions to build your first page:
- In your source folder, create a folder named pages
- Build your first React component

pages/index.js

import React from 'react'

export default () =>
<div>
<p>Hello Next.js</p>
</div>

By visiting the homepage of your website, you should see Hello Next.js displayed there.
With a page-based routing, you have 1 component for each page. This is a common system that you can find in static site generators.

Building a dynamic routing

Next.js’ built-in page-based routing is easy to use, but most of the time you need dynamic URLs. Imagine that you need a dynamic parameter that you want to manage on the route level and inject it in your component as props. This is where dynamic routing becomes necessary.

Next.js has a built-in server implementation which will need to be replaced to implement dynamic routing.
As you’ll see soon, you’re going to set up a simple Express server. Express is a famous tool for the Node.js community because of its simplicity and its powerful url matching system.

Configuring your routes

In this example, you will discover next-routes, an easy tool for handling both server and client side routing.

Because we use an Express server implementation, your routes will have the exact same syntax in terms of pattern matching which you can find in details in the Express documentation.

routes.js

const routes = require('next-routes')

module.exports = routes()
.add('index', '/')
.add('products', '/products')
.add('product', '/products/:uid')
.add('bloghome', '/blog')
.add('blogpost', '/blog/:uid')
.add('preview', '/preview')
.add('notfound', '/*')

In the example above, you have two dynamic routes for aproduct and a blogpost. In both cases, we’re passing a uid parameter to the corresponding component.

Setting up your server

Once your routes are ready, your just need to set up your server and provide your routing configuration to it.

server.js

const  next  =  require("next")
const routes = require("./routes")
const PORT = parseInt(process.env.PORT, 10) || 3000
const app = next({ dev: process.env.NODE_ENV !== "production" })
const handler = routes.getRequestHandler(app)

const express = require("express")

app.prepare().then(() => {
express()
.use(handler)
.listen(PORT, () => process.stdout.write(`Point your browser to: http://localhost:${PORT}\n`))
})

Updating your build environment to include your server

Once you’ve implemented an Express server, you need to take it into account and update your build scripts.

package.json

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

Navigating in your application

This is where next-routes becomes really convenient.
By exporting your routes, you also have a fully featured router which you can use to navigate through your application without worrying if you’re on the server side or on the client side.
You can navigate between components in two ways:

1. The declarative way with a Link component:

pages/blogpost.js:

import React from 'react'
import { Link } from './routes'

export default ({ query }) =>
<div>
<div>Welcome to my blogpost with a dynamic UID: {query.uid}</div>
<Link to='bloghome'>
<a>Blog Home</a>
</Link>
</div>

Your can observe two things here.
First, your have a variable named query coming from the props which contains your dynamic parameter uid. It comes from your routing system.
Second, your have a component Link coming from your routes configuration. It allows you navigate between components without doing a full page reload.

2. The imperative way with a Router object:

import React from 'react'
import {Router} from './routes'

export default class extends React.Component {
handleClick () {
Router.pushRoute('bloghome')
}
render () {
return (
<div>
<div>Welcome to my blogpost with a dynamic UID: {this.props.query.uid}</div>
<button onClick={this.handleClick}>Blog home</button>
</div>
)
}
}

In this case, you’re getting the Router object from your route configuration. It allows you to manage your routing programmatically instead of doing it from the template.

Fetching external data asynchronously

Querying your content from an external API

Once your application is set up, you’ll want to feed it with some actual content, most likely coming from an external source. This is painful to do so in a Universal App but Next.js offers an easy way to handle it.

Along with the well known React component lifecycle, each page component is able to implement an async function getInitialProps which provides the fetched data as props in your component.

import { Link } from  './routes'
import { Client, linkResolver } from '../components/prismic'
import { RichText } from 'prismic-reactjs'

export default class extends React.Component {
static async getInitialProps({ req, query }) {
const blogpost = await Client(req).getByUID('blog_post', query.uid)
return { blogpost }
}

render() {
return (
<div>Blogpost name: { RichText.render(this.props.blogpost.data.name, linkResolver) }</div>
)
}
}

In the simple example above, we asynchronously fetched data from an external source and populated it into our component.

Gaining speed by prefetching your content

Next.js also has an embedded mechanism to prefetch all the related pages to help speed up your website and offer a really fast user experience.
To prefetch a link, all you have to do is add a prefetch property to the link.

<Link prefetch to={`/post/${show.id}`}>
<a>{show.name}</a>
</Link>

Basically, for each Link with a tag prefetch, Next.js pre-fetches the component’s JSON representation in the background, via a  ServiceWorker. If you navigate around, odds are that by the time you follow a link or trigger a route transition, the component has already been fetched. Furthermore, since the data is fetched by a dedicated method getInitialProps, you can pre-fetch aggressively without triggering unnecessary server load or data fetching.

A closer look at the Next.js implementation of Universal App

Universal Apps are a kind of application which are built purely in JavaScript with Node.js on the server side.
This architecture became really famous with React but is currently available for many client-side frameworks or libraries such as Angular, Vue.js, etc.
Basically, It allows you to share your components between the server and the client so you can render it on both sides.

How your components are rendered on the first load

The first load is always handled by the server which renders the component as static markup and then sends it to the client to simply render that HTML.
Once it’s rendered, the browser loads the JavaScript and applies all the browser handlers to your components such as ‘onClick’, ‘onChange’, etc.
Doing so prevents you from waiting for your client to load your JavaScript and then render your component.

How to feed your components with data on both sides

Most of the time, your components will rely on external data to be rendered. External data also usually means asynchronous data.

Generate HTML for the client

On the server side, Next.js accesses the query required for the requested page so it can compute this query and provide the data to your component before sending it to the client.
Once the query is done, Next.js will simply send a static markup to your browser and re-execute React to bind all the browser events.

Recover the data from the client

To avoid your component making the same query twice, Next.js will serialize the data along with the HTML and provide it to your components.

Next.js exposes a static asynchronous getInitialProps function for each page component and then serializes the fetched data in a script tag. On the client side, it reads the data in the window and provides it to the components as props. This mechanism is called rehydratation.

Next.js for what purpose?

Here at Prismic, we consider Next.js as a convenient library for people who are used to React but struggle when it comes to SEO and complex architecture to address it.
It’s quite easy to get started but also very powerful since it offers a comfortable space for customization.
Integrating marketing content and SEO pages has become very productive with Next.js.

You can find a fully functional example of a coffee shop built with Prismic and Next.js, and all the sources here on GitHub.
All the examples above are inspired by this sample.

Arnaud Lewis

Backend engineer at Prismic. Arnaud drives motorcycle to work no matter the weather, and plans to start a 80s-themed radio station one day.