Implementing Server Side Rendering using React and Express
Tags: react, express, ssr, tutorial
Published: 2020-02-16 06:00:55

Server Side Rendering let us render a basic skeleton of our page server-side and send it to the user. This provides us with benefits like Search engine optimisation and faster initial page load. Let's start from scratch. You can clone this whole project from [here](https://github.com/achhapolia10/forum/tree/64adffcd8c320805b23e880363b6585545d79268).

## Prerequisites
* Nodejs
* npm
* Your prefered editor

## Basic layout of the project directory
<div class="highlight">
<pre class="highlight plaintext">
<code>
|-- project
     |-- <a href="#babelrc">.babelrc</a>
     |-- package-lock.json
     |-- package.json
     |-- <a href="#webpackconfigjs">webpack.config.js</a>
     |-- src
          |-- <a href="#srcserverjs">server.js</a>
          |-- components
          |   |-- entrypoints
          |   |     |-- <a href="#srccomponentsentrypointsindexjsx">index.jsx</a>
          |   |-- pages
          |         |-- <a href="#srccomponentspagesindexjsx">index.jsx</a>
          |-- routes
          |     |-- <a href="#srcroutesindexjs">index.js</a>
          |-- views
               |-- pages
               |    |-- <a href="#srcviewspagesindexejs">index.ejs</a>
               |-- partials
</code>
</pre>
</div>

## Setting up babel and Express.js

Following command initializes a node package in the current directory.

```
$ npm init 
```

Next, we install Express and ejs. We will be using ejs as express view engine to write base HTML for the page.

```
$ npm install express ejs compression react react-dom
```

Following commands install various packages required for babel.

```
$ npm install --save-dev @babel/core @babel-cli @babel/node @babel/plugin-proposal-class-properties @babel/preset-env @babel/polyfill @babel/preset-react nodemon webpack webpack-cli
```

#### Details about above packages
* *@babel/core:* Babel's core runtime
* *@babel/node:* Babel node is a cli that works the same as the Node.js CLI, with the added benefit of compiling with Babel presets and plugins before running it
* *@babel/plugin-proposal-class-properties:* plugin required by Babel to support classes
* *@babel/preset-env :* Babel preset that allows using latest JavaScript.
* *@babel/preset-react:* Babel preset required for react.
* *@babel/polyfill:*  Needed by react to when using promises
* *@babel/cli:* Optional. Can be used later to compile to the target the application (server and react files) for node to deploy in production.

## Configure the babel

Create a file **.babelrc** in the project directory. If you have any doubt where a file is to be placed refer to the [directory structure](#basic-layout-of-the-project-directory). Keeping a good structure file is a very important part of code maintenance and is required in huge projects.

#### .babelrc
```json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ],
    [
      "@babel/preset-react"
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-class-properties"
    ]
  ]
}
```

## Setup Server

Create *src* directory under the project. Add **server.js** file to it.

#### ./src/server.js
```javascript
import express from "express";
import compression from "compression";
import index from "./routes/index";
import path from "path";

// Server var
const app = express();

// View engine setup
app.set("views", path.join(__dirname,"views"));
app.set("view engine", "ejs");

// Middleware
app.use(compression());
console.log(__dirname);
app.use(express.static(__dirname + "/public"));

//Routes
app.use("/", index);

const port = process.env.PORT || 3000;

app.listen(port, function listenHandler() {
    console.info(`Running on ${port}`)
});
```
Create a *routes* directory user *src*. Add **index.js** file to routes directory. Add files which implement different routes to this directory.

#### ./src/routes/index.js
```js
import express from "express";

const router = express.Router();

router.get('/', async (req, res) => {
    let reactComp = ""; // So that program doesn't break
    res.status(200).render('pages/index', {reactApp: reactComp});
})
;

export default router;
```

Create a directory *views* which will contain the templates for ejs. Under *views*, two directories should be made.
* *pages:* this folder contains the templates for pages that will be representing the skeleton for different pages.
* *partials:* this folder contains various partials like headers, footers, etc.

Create a file **index.ejs** at *./src/views/pages*.

#### ./src/views/pages/index.ejs
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Smoke -h</title>
</head>
<body>
<div id="root"><%- reactApp %></div>
<script src="/index.js" charset="utf-8"></script>
<script src="/vendor.js" charset="utf-8"></script>
</body>
</html>
```
The above-mentioned index.js and vendor.js files are generated by webpack. We will discuss them later.

Add the following script in **package.json** file.

```json
{
    ....
    ....
"scripts": {
    "webpack": "webpack -wd",
    "dev": "nodemon --exec babel-node src/server.js",
  }
    ....
    ....
}
  ```

I'll explain the webpack script in some time.

The server is ready to run. Run the following command.

` $ npm run  dev`

Open a browser and go to http://localhost:3000. You will see a blank page for now.

## Creating React Page

Create a *components* directory under src. This(*components*) directory has 2 more sub-directories *pages* and *entrypoints*. These directories are locations for our react components. We can create additional folders and file later here as we add more components.
*  *pages:* This directory stores the final pages that need to be rendered.
* *entrypoints:* This directory store the files that will hydrate our pages. I'll explain this concept later.

Create file **index.jsx** under *project/src/components/pages/*. This file is the React page for index route.

#### ./src/components/pages/index.jsx
```jsx

import React from "react";

class Index extends React.Component {
    constructor() {
        super();
        this.state = {name: "a", email: ""}
    }

    onFormSubmit = (event) => {
        event.preventDefault();
    }

    onNameChangeHandler = (event) => {
        this.setState({name: event.target.value});
    }

    onEmailChangeHandler = (event) => {
        this.setState({email: event.target.value});
    }

    render() {
        return (
            <div>
                <h1>Smoke -h</h1>
                <form onSubmit={this.onFormSubmit}>
                    <div>
                        <label htmlFor={"name-input"} defaultValue={"Name"}>Name: </label>
                        <input name={"name-input"} onChange={this.onNameChangeHandler} type={"text"}
                               value={this.state.name}/>
                    </div>
                    <br/>
                    <div>
                        <label htmlFor={"email-input"} defaultValue={"Email"}>Email: </label>
                        <input name={"email-input"} onChange={this.onEmailChangeHandler} type={"email"}
                               placeholder={"email"} value={this.state.email}/>
                    </div>
                    <br/>
                    <div>
                        <button type={"submit"}>Submit</button>
                    </div>
                </form>
                <span><h5>Name: {this.state.name}</h5></span>
                <span><h5>Email: {this.state.email}</h5></span>
            </div>
        )
    }
}
export default Index;
```

## Rendering React Page on Server Side

We now use renderToString() to render the react components in our <a href="#srcroutesindexjs">./src/routes/index.js</a> file.

#### ./src/routes/index.js
```js
import express from "express";
import React from "react";
import {renderToString} from "react-dom/server"
import Index from "../components/pages/index"


const router = express.Router();

router.get('/', async (req, res) => {
    const reactComp = renderToString(<Index/>);
    res.status(200).render('pages/index', {reactApp: reactComp});
})
;

export default router;
```

Now if we run the server. We get our react components rendered. But the page is not reactive.

![Without Hydrate](https://drive.google.com/uc?export=view&id=1OYMFY20O5XMMRF-9c_H80B4dbPTAv9XE)

## Hydrating the React Pages

ReactDOM.hydrate() is the function that brings ours react page alive. When we call ReactDOM.hydrate() it preserves the server-rendered markup and attach event handlers to them and provide an entry point into the react application.

To create this entrypoint we create **index.jsx** under *./src/components/entrypoints/index.jsx*.

#### ./src/components/entrypoints/index.jsx
```jsx
import React from "react";

import {hydrate} from "react-dom";

import Index from '../pages/index'

hydrate(<Index/>, document.getElementById("root"));

```

But doing so is not enough. This part of the code should run on the client-side. That is why use webpack to bundle this file with libraries to send it to frontend side. 

## Webpack

Webpack is a bundler. A module bundler is a tool that takes pieces of JavaScript and their dependencies and bundles them into a single file, usually for use in the browser.

To use webpack we need to create a **webpack.config.js** file under our project directory.

#### webpack.config.js

```js
const path = require("path");

const config = {
    entry: {
        vendor: ["@babel/polyfill", "react"], // Third party libraries
        index: ["./src/components/entrypoints/index.jsx"]
        /// Every pages entry point should be mentioned here
    },
    output: {
        path: path.resolve(__dirname, "src", "public"), //destination for bundled output is under ./src/public
        filename: "[name].js" // names of the bundled file will be name of the entry files (mentioned above)
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                use: {
                    loader: "babel-loader", // asks bundler to use babel loader to transpile es2015 code
                    options: {
                        presets: ["@babel/preset-env", "@babel/preset-react"] 
                    }
                },
                exclude: [/node_modules/, /public/]
            }
        ]
    },
    resolve: {
        extensions: [".js", ".jsx", ".json", ".wasm", ".mjs", "*"]
    } // If multiple files share the same name but have different extensions, webpack will resolve the one with the extension listed first in the array and skip the rest.
};

module.exports = config;
```

Now run

` $ npm run webpack`

On another shell run 

` $ npm run dev `

And now when we visit http://localhost:3000 our react app has become dynamic.:tada::tada::confetti_ball:

Feel free to comment if you have any doubts regarding this.
![final](https://drive.google.com/uc?export=view&id=1cPRPf8LwHewP1q8LOxLAzZWbzmI2NdC7)