Server-Side Rendering a React App Using Express.js
By: Putra Aryotama
Tags: JavaScript, Web Development, Expressjs, Programming, React

Server-Side Rendering a React App Using Express.js

Photo by Science in HD on Unsplash

You have setup and created your react app (using create-react-app, other boilerplates, or even creating it from scratch). Now you’re trying to serve it to the web so people can experience your app. Sure, for easy deployment and minimal setup you can go for the already established platforms such as Netlify, Heroku, Firebase Hosting, Github Pages, or many others. This article, however, is not about that. This article is about setting up your own server and deploying your site/app using good ol’ Express.js.

Why, you may ask? For learning purposes, of course. Having an understanding of how servers deliver your app/site is beneficial for your programming fundamentals. It may also aid in your future work in deploying (and configuring) your sites.

In this article, I will focus on how server-side rendering works in serving a react app using Express.js. I will first explain the concept then dive into example project code. To gain a better understanding, you should also check out how client-side rendering works as a comparison. That way, you will have a much clear understanding of how each method works and gain a whole view of both client-side and server-side rendering solutions. I have written an article on client-side rendering. So check it out:

By the end, you should have a clear understanding of what server-side rendering is and how to apply server-side rendering solutions with Express.js. In addition to that, you should also gain a general understanding of how servers work and how sites/apps are deployed to servers.

Server-side Rendering

As opposed to client-side rendering, server side-rendering renders the react component in the server before being sent to the client. In a server-side rendering process, the steps from the initial request to the app being available for interaction goes like this:

Request for the page is sent to the server from the client. The server responds by rendering the react component in the server before being sent to the client. Client receives the html (with the react component) and displays it in the browser. Client also download the required JavaScript file (containing the react app). After the JavaScript file is downloaded, it will rehydrate the html that is currently in display with the react app from the JavaScript file.

Notice that in the second step the react component is rendered in the server before being sent to the client. Hence, there will be no “white page” when waiting for all the needed JavaScript files to be downloaded which typically happens in a client-side rendering process.

The biggest upside of having a pre-rendered React app in the server is SEO (search engine optimization). When a typical search engine crawls a website, it will index the html response from every URL it crawls. Search engine crawlers generally skip the JavaScript code or files. That’s why having a client-side rendered React app may be damaging for SEO.

At the time of writing, only Google search engine is known to crawl, parse, and index JavaScript (with limitations). According to an article in moz.com from 2017, the JavaScript indexing performance of several search engines are as follows:

The Project

You can use your existing React app if you already have one, or you can start a new one using create-react-app and leaving the main code inside src directory as is (as we’re focusing on the back end). I , however, suggest you try and start a React app from scratch which can enhance your understanding of the bits and pieces that goes inside a React app.

To learn how a React app is built from scratch, you can follow the link below and read my earlier article:

I will use the code from the article above where the React app is created from scratch. If you’ve followed along, your project directory should at least have the following folder structure and files:

react-app/

|--public/

| |--index.html

| |--bundle.js

|--src/

| |--app.js

|--babel.config.js

|--package-lock.json

|--package.json

|--webpack.config.js

This article will assume that you already have a basic understanding of JavaScript, Node.js, Webpack, Babel and React. If not, for basic setup (with Node, Webpack, Babel, and React), I recommend you (again) to follow the link above to start a React app from scratch.

Webpack Configurations

We will first edit and configure the existing webpack.config.js file. Inside the file, an object with the keys entry , output , module , and devServer is defined and exported as a webpack configuration. This webpack configuration, however, is specifically to configure, transform and bundle our client-side React files to be presentable by the browser.

For server-side rendering, we also need to configure, transform, and bundle our server files as it’s using the latest ES syntax and React JSX. So instead of exporting an object as our webpack configuration, we will be exporting two objects. One for the server files configuration and the other for client files configuration. In both objects, we will keep the entry , output , and module configurations more or less the same.

Client configuration

The client configuration for webpack compilation in this project should be the same as the default React web configuration as specified in our ‘create react app from scratch’ article. There you should find the explanation for setting up a React webpack configuration. In this project, we wrap that configuration in an object called clientConfig :

client configuration

Server configuration

server configuration

As I’ve promised, the entry , output , and module configuration is more or less the same. However, we need to change the entry and output files. We point the entry file to ./server/index.js and the output file to ./dist/index.js . You may notice that we do not yet have a server directory with index.js file inside. We will create this later.

Beside entry , output , and module keys, we also need to define the target , externals and node keys. The target key specifies the target environment in which our file will be compiled to. In our case, because it is a server file, we set the target environment to node . That way, webpack will compile the file for usage in a node environment (our backend). When compiling, no built-in modules (such as fs or path ) will be touched.

The externals key provides a way to exclude any dependencies we wish to not be included in the output bundle. Why? because of size concerns. Instead, the output bundle relies on that dependency to be present in the consumer’s (any end-user application) environment. In our case, we specified the externals to be the webpack-node-externals library. This library creates an external function that excludes all node_modules dependencies.

The last key that we need to specify is the node key. The node key specifies the configuration of certain node global variables. In this case we configure the __dirname variable. Setting it to false will ensure that dirname behaves normally in a node environment (aka it will return the absolute path of the output file).

Export webpack configuration

After we specified both server and client webpack configuration, we export both objects as follows:

configurations export

React Codes

From our previous project, we only have one file for our React code: src/app.js . Inside that file, we specified both the component ( App ) and the rendering of that App component:

src/app.js

We will now separate the component and the rendering into two separate files. The first file ( src/hello.js ) houses the <Hello /> component, whereas the second file ( src/app.js ) houses only the rendering of our React component ( <Hello /> ).

src/hello.js

src/app.js

Notice that instead of ReactDOM.render(...) , we use ReactDOM.hydrate(…) . The hydrate method is the same as render , but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer in the server (explained later). The use of render method to hydrate server-generated container (instead of the hydrate method) may be slow and will be removed in React 17.

Server Code using Express.js

Now, we will create our backend server code using Express.js in a file called server/index.js . Express.js styles itself as “fast, unopinionated, minimalist web framework”. Express is a go-to JavaScript framework if you’re building a back end for your project. To install Express.js, run the following command:

npm install express

After installing Express, create a new directory at the root of the project named server .

mkdir server

This directory is where we will setup the Express back end. By now, you should at least have three directories at the root called public , src , and server .

Inside the server directory, create a file named index.js :

touch server/index.js

Your server directory should now look like this:

react-app

|-server

| |-index.js

|...

In server/index.js , add the following code:

server/index.js

We require the express module and call the top express function, express() . We also set the desired port number, which is 3000. We instruct express to start the server and listen to the port that we’ve set. The console will output ‘Server now listening at http://localhost:3000.

That is the basic setup for our Express server. To render our React app to the server before sending it to the client, we add the following code between the global variable assignment ( const port = 3000 ) and app.listen(…) :

Server code inside server/index.js

Between port variable definition and app.listen() , we need to code the actual request handler and the response to be sent by the server.

First, we need to further require and import several global variables: path , ReactDOMServer , React , and the Hello component. path is a built-in node module for working with file and directory paths, ReactDOMServer is a React object to render components to static markup typically used in a node server, React is the default React JavaScript library, and lastly the Hello component is the component to be rendered in the server that we created inside the src directory earlier.

Two things that need to be highlighted here are the ability to use React components and ES6 import in our backend server code. Both are supported because this server file will also be transformed and compiled using webpack and babel to a JavaScript file that can be read in a node environment. We have set the webpack configurations earlier.

An Express.js application is basically a series of middleware and routing functions. Middleware functions tell Express.js what to do with incoming requests. Whether to transform the request data, sending back response, or passing it to the next middleware. The express().static middleware instructs express to serve static files from a specified directory. Here, we specified the /static route to serve static files from the public directory. The public directory is where our client-side React file that has been compiled and transformed by webpack lives.

Lastly, we specify the home (‘/’) route where the server will send back the server-rendered Hello component wrapped inside the basic html template assigned to the html variable. After the Hello component has been imported, it is rendered into an html string using the renderToString() method of ReactDOMServer . The Hello component string is then interpolated inside the basic html template. That way, the server doesn’t return only the basic html template (as is in client-side rendering), but also with a React component that has already been rendered in the server (hence the name ‘server-side rendering’).

Another thing to note is the script tag inside the html template. The script refers to the file from the ‘/static’ route that we defined earlier as our static file path. Because the script tag is called from the client browser, it won’t be able to access file directly to the filesystem inside the server. Instead it could only access files through valid routes that we have defined in the server.

Nodemon

Nodemon is a tool in developing Node-based application that helps to automatically restart the said application when file changes in the directory are detected. That way, we would not have to manually cancel and restart our express server every time we want to see how the changes we’ve made takes place. Install Nodemon globally (recommended) by typing the following command:

npm install -g nodemon

You can now use Nodemon on other projects in your machine as it is installed globally.

NPM Scripts

To make our development easier we need to define several npm scripts. Npm scripts are defined inside package.jsone at the root of our project folder. The existing scripts that we already have are as follows:

existing scripts

We add another script to start our express server named ‘serve’. The script will start our server using nodemon. We will not directly use our server file from server directory as it contains React and ES6 code that could not be run inside a node environment. Instead, we will use the server file that has been transformed and compiled using webpack and babel. That file lives inside the dist directory.

edited scripts

Running the Server

Before running the server, we should build and compile our server and client code (from src and server directories respectively to public and dist directories). To build and compile, type npm run build command in the terminal. After running the command, our folder structures should look like this:

react-app/

|--public/

| |--bundle.js

|--dist/

| |--index.js

|--server/

| |--index.js

|--src/

| |--app.js

| |--hello.js

|--node_modules/

|--babel.config.js

|--package-lock.json

|--package.json

|--webpack.config.js

Now, to run the server, we type npm run serve command in the terminal.

npm run serve

Open up the browser and go to ‘http://localhost:3000’,

localhost:3000

Voila! Our React app should appear. How do we know that our app is being rendered server side? To find out, open the browser developer tools. If you are using chrome, press ctrl + shift + I on your keyboard to open up chrome dev tools. Click the sources tab. On the left, click (index) to open up the source html for our home page (http://localhost:3000/).

home (‘/’) source html for server-side rendered app

As you can see, our Hello component (underlined) is present inside the html code sent from the server! This is different from client-side rendered app, where the html sent from the server only contains the container element (in our examples is the div element with the id of root ). Components inside the root container would then be rendered by the client browser only after all the bundled React code have been downloaded.

home (‘/’) source html for client-side rendered app

Summary

In this article, I gave a little bit of background on server-side rendering and dived into actual React and Express.js project code to explain server-side rendering process. Before sending back the html response, React component(s) is(are) rendered to html in the server. We do this by using the renderToString method of ReactDOMServer .