React Server Side Rendering - Beginners Guide
React Server Side Rendering - Beginners Guide

In this post you will learn what is server side rendering inside React, how you can implement it and what are the benefits of it.

When we render a web page we can do it in different ways. The oldest and the fastest way is simply to render an HTML page which will be rendered by the server. For example my own website is fully rendered by the server.

Website

Most importantly here the whole markup is rendered by default inside the page and we can see it.

Source code of ssr

Another approach is to render a page by frontend frameworks like React, Angular or Vue. Our backend doesn't do anything. It simply delivers the empty page with out Javascript. Then our Javascript on the client builds the page.

Frontend source code

Here we don't have any markup except of the container. And at the bottom we load all our scripts.

The main benefit of client build application is that you can without page reload jump between pages.

This is why nowadays we build lots of website just with client.

SPA Problems

But with this approach we don't have any markup before our Javascript will render something. Which essentially means - our page is empty. It has several problems.

Our initial page is fully empty. If I will disable Javascript you can see that we get an empty page. Which means if our client project loads really long then our user won't see anything and will simply leave the page.

The second problem is with SEO. For example Google can't read client Javascript because it is not rendered in markup by default. If we are talking about my own website which is rendered by backend server Search Engine can simply parse the whole markup.

And now we are coming to the question. How can we render our DOM page and also have benefits of client frameworks?

Server side rendering

As you can imagine it is really difficult to render markup on the backend and then try to apply client framework. This is a really difficult task.

This is why we are getting another really popular approach which is called server side rendering. We are talking about using some client framework like React and then the same React on the backend where we are typically using Node just because then we are getting the same Javascript on the client and on the server that can be executed with minimum differences.

So what we are getting with Server side rendering? We will see our markup on the initial page load because our page will be built on the backend by NodeJS and React.

Secondly search engines can read on initialize our page and index this page correctly.

But what problems do we have with server side rendering? First of all it can be expensive for the backend server just because you must throw React fully inside backend, parse all your components there, prepare everything, render it and deliver to the page.

Secondly is it much more difficult to setup server side rendering correctly in comparison to just client.

Implementation

But enough talking. Let's try on the simple example with 2 routes Home and About to convert it to server side rendering.

Our first step here will be to install necessary dependencies.

yarn add express @babel/preset-env @babel/preset-react @babel/register ignore-styles

After installation of all this packages let's look on our project.

const App = () => {
  return (
    <div className="app">
      <h1>Hello Monsterlessons</h1>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/about" element={<About />}></Route>
      </Routes>
    </div>
  );
};

It's just a component with 2 links and 2 routes where we render 2 additional components.

Now we want to create a server folder with index.js

// server/index.js
require("ignore-styles");

require("@babel/register")({
  ignore: [/(node_modules)/],
  presets: ["@babel/preset-env", "@babel/preset-react"],
});

require("./server");

This is the file that we want to execute on the server. We ignore styles from our project and use babel to setup correct presets. After this we load server.js file where the whole logic of our backend will be written.

// server/server.js
import express from "express";
const app = express();

app.use('*', (req, res) => {
  res.send('server side')
})

app.listen(3005, () => {
  console.log("App is launched");
});

Here we just created an express server. As we used babel we can safely write imports here. For all our routes we are rendering a message server side. No we can try to start our server with node server/index.js.

As you can see in browser our string is rendered on any route.

Our next step is to build React application so we can use assets in our backend.

yarn build

Now we want to read the content of index.html inside our build folder.

app.use("*", (req, res) => {
  fs.readFile(path.resolve("./build/index.html"), "utf-8", (err, data) => {
    if (err) {
      console.err(err);
      return res.status(500).send("Some error happened");
    }

    return res.send('server side', data);
  });
});

As you can see in terminal every time when we load the page the content of index.html is shown.

Now we want to render React application here.

import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import App from "../src/App";

app.use("*", (req, res) => {
  fs.readFile(path.resolve("./build/index.html"), "utf-8", (err, data) => {
    if (err) {
      console.err(err);
      return res.status(500).send("Some error happened");
    }

    const html = ReactDOMServer.renderToString(
      <StaticRouter location={req.url}>
        <App />
      </StaticRouter>
    );

    return res.send(
      data.replace('<div id="root"></div>', `<div id="root">${html}</div>`)
    );
  });
});

We used here ReactDOMServer.renderToString which converts React DOM tree to html. We provided inside StaticRouter which is a backend router from react-router-dom package which we typically use on the client. As a result we are getting an html which we want to put inside container in markup of index.html and respond with it.

As you can see in browser our index.html is rendered. But we have errors in the console regarding loading of CSS and HTML because we always respond with our HTML instead.

app.use("^/$", (req, res) => {
  ...
});

app.use(express.static(path.resolve(__dirname, "..", "build")));

Here we added a static files from build folder. But we will also load our index.html as an static asset which we don't want. This is why instead of * in our app.use we must provide a regular expression which will only render routes which begin with the /.

As you can see in browser our markup is there and our project is working as expected.

Hydration

The last thing that we need to check is adding hydration on the client.

// src/index.js

ReactDOM.hydrate(
  ...
)

Here we changed React.render to React.hydrate. The difference is that hydrate won't try to render any initial markup but will just bind events to it.

Other ways

If you think that server side rendering inside React is too complicated you can use a framework around React which solves exactly the same problem. It is called NextJS. It is not as flexible as custom React rendering but can be enough for some types of applications. This is the framework which renders the whole project automatically on the backend.

And actually if you are interested to learn how to build real React project from start to the end make sure to check my React hooks course.

📚 Source code of what we've done