Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Next.js Quick Start Guide

You're reading from   Next.js Quick Start Guide Server-side rendering done right

Arrow left icon
Product type Paperback
Published in Jul 2018
Publisher Packt
ISBN-13 9781788993661
Length 164 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Kirill Konshin Kirill Konshin
Author Profile Icon Kirill Konshin
Kirill Konshin
Arrow right icon
View More author details
Toc

Table of Contents (9) Chapters Close

Preface 1. Introduction to Server-Side Rendering and Next.js 2. Next.js fundamentals FREE CHAPTER 3. Next.js Configuration 4. Next.js Data Flow 5. Application Life Cycle Handlers and Business Logic 6. Continuous Integration 7. Containers 8. Other Books You May Enjoy

How to do server-side rendering with React

Luckily, React is built with two main concepts in mind: it's state driven and it is capable of rendering to plain HTML. React is often used with React Router, so let's take this and explain how to render your React app on a server.

React-based server-side rendering frameworks, why Next.js

Nowadays, there are few competitors in the React-based server-side rendering market. We can divide them into the following categories:

  • Drop-in dynamic solutions (Next.js, Electrode, After)
  • Drop-in static solutions (Gatsby, React Static)
  • Custom solutions

The main difference between first two approaches is the way the app is built and served.

A static solution makes a static HTML build (with all possible router pages), and then this build can be served by a static server such as Nginx, Apache, or any other. All HTML is pre-baked, as well as the initial state. This is very suitable for websites with incremental content updates that happen infrequently, for example, for a blog.

The dynamic solution generates HTML on the fly every time the client requests it. This means we can put in any dynamic logic, or any dynamic HTML blocks such as per-request ads and so on. But the drawback is that it requires a long-running server.
This server has to be monitored and ideally should become a cluster of servers for redundancy to make sure it's highly available.

We will make the main focus of this book dynamic solutions, as they are more flexible and more complex but also require deeper understanding.

Lets dive deeper into a custom solution using only React and React Router.

Let's install the router and special package to configure routes statically (it's impossible to generate purely dynamic routes on a server):

npm i --save react-router-dom react-router-config

Now, let's configure the routes:

// routes.js
const routes = [
{
path: '/',
exact: true,
component: Index
},
{
path: '/list',
component: List
}
];
export default routes;

The main app entry point should look like this:

// index.js
import React from 'react';
import {render} from 'react-dom';
import BrowserRouter from 'react-router-dom/BrowserRouter';
import {renderRoutes} from 'react-router-config';
import routes from './routes';

const Router = () => {
return (
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
)
};

render(<Router />, document.getElementById('app'));

On the server, we will have the following:

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { renderRoutes } from 'react-router-config';
import routes from './src/routes';

const app = express();

app.get('*', (req, res) => {
let context = {}; // pre-fill somehow
const content = renderToString(
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
);
res.render('index', {title: 'SSR', content });
});

But, this will simply render the page with no data. In order to prepopulate data into the page, we need to do the following, both in the component and in the server:

  1. Each data-enabled component must expose a method that the server should call during route resolution
  2. The server iterates over all matched components and utilizes exposed methods
  3. The server collects the data and puts it into storage
  4. The server renders the HTML using routes and data from storage
  5. The server sends to the client the resulting HTML, along with data
  6. The client initializes using the HTML and prepopulates the state using data

We intentionally won't show steps 3 and onward, because there is no generic way for pure React and React Router. For storage, most solutions will use Redux and this is a whole another topic. So, here we just show the basic principle:

// list.js
import React from "react";

const getText = async () => (await (await fetch('https://api.github.com/users/octocat')).text());

export default class List extends React.Component {

state = {text: ''};

static async getInitialProps(context) {
context.text = await getText();
}

async componentWillMount() {
const text = await getText();
this.setState({text})
}

render() {
const {staticContext} = this.props;
let {text} = this.state;
if (staticContext && !text) text = staticContext.text;
return (
<pre>Text: {text}</pre>
);
}

}
// server.js
// all from above
app.get('*', (req, res) => {
const {url} = req;
const matches = matchRoutes(routes, url);
const context = {};
const promises = matches.map(({route}) => {
const getInitialProps = route.component.getInitialProps;
return getInitialProps ? getInitialProps(context) : Promise.resolve(null)
});
return Promise.all(promises).then(() => {
console.log('Context', context);
const content = renderToString(
<StaticRouter location={url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
);
res.render('index', {title: 'SSR', content});
});
});

The reason why we are not covering those aspects is because even after heavy development, it becomes obvious that the custom solution still has quirks and glitches, primarily because React Router was not meant to be used on a server, so every custom solution always has some hacks. Even the authors of React Router say that they decided not to use server-side rendering in their projects. So, it would be much better to take a stable/standard solution that was built with server-side rendering in mind from day one.

Among other competitors, Next.js stands out as one of the pioneers of this approach; this framework is currently the most popular. It offers a very convenient API, easy installation, zero configuration, and a huge community. Electrode may be more flexible and powerful than Next.js, but it has extremely complicated configuration. After this is a Next.js-alike framework, which is built on top of React Router, Helmet and other familiar libraries, but the community is still relatively small so far, although it definitely worth to mention.

A full comparison is available in my article here: https://medium.com/disdj/solutions-for-react-app-development-f9fcaeba504.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime