Server-Side Rendering in React — ExpressJS

How to configure React application to be rendered on both the server and the client using the ExpressJS framework.

Bartłomiej Dybowski
Mar 21 · 13 min read

In my previous article about Server-Side Rendering in React, I have briefly described the idea of isomorphic and universal applications. I’ve also shown the advantages and disadvantages of this approach. Today, I will get to the point and show you how to configure a universal React app using the ExpressJS framework!

First, I will describe how to configure the app for the client, and then I will transform it to make it also work server-side. It will be the most basic example of the React universal application. Later, in the following issues of this series, I will expand my code by the use of Redux and react-router.

Starting point — an example React component

To achieve the above goals, we need two starting points in our app. For the server, it will be the server.js file, and for the client.js client, it will be the file. We will discuss these two files in a second, but first, let’s create our main React component. It may look like below (App.js):

An example of an App.js component that shows the initial text and contains a button to change it.
An example of an App.js component that shows the initial text and contains a button to change it.
App.js

As you can see, it is a simple component that gets some initial text from it’s props, assigns it to the state, and then renders it — and we want it to be done on the server.

Additionally, there is a button that changes the value of the state’s text property. This interaction will be done on the client.

Client-side rendering

An example of the client.js file that is responsible to render the React application into the “app” container.
An example of the client.js file that is responsible to render the React application into the “app” container.
client.js

If you have been working with React for some time, you should be familiar with the above code. Thanks to the render method of the ReactDOM object we can inject the App component into the HTML element with the “app” identifier. It only happens after our JavaScript file is loaded in the browser (a blank page is rendered before). Please note how the initial text is passed into the component.

Of course, if we want to make all of this work, we have to add a few packages to our project:

Command that adds react and react-dom to your project.
Command that adds react and react-dom to your project.
Add react and react-dom to your project

Please note that I don’t use the --dev parameter here. It’s because I will need these packages not only in the client-side’s “bundle” but also on the server.

Client-side preparation

Let’s start by installing all the necessary packages:

Command adding webpack, babel and html-webpack-plugin to the project.
Command adding webpack, babel and html-webpack-plugin to the project.
Add webpack, babel and html-webpack-plugin

We will also need some Babel presets (predefined sets of Babel plugins):

Command adding babel presets to the project.
Command adding babel presets to the project.
Add Babel presets

And last but not least, we should install Babel polyfill package (for both the client and the server so no--dev parameter):

Command adding babel polyfill to the project.
Command adding babel polyfill to the project.

Why do we need this? Well, Babel can translate the JavaScript syntax, but subsequent versions of ECMAScript introduce various native methods or global objects (e.g. Promise). Thanks to babel-polyfill we can emulate the whole ES6+ environment.

Ok, now we are ready to add the index.html file to our project:

An example index.html file with the “app” container in its body.
An example index.html file with the “app” container in its body.
index.html

We will use this file as a starting point of our application — at least for now — when we add the server-side configuration, this file won’t be necessary anymore. Please note, the “app” container — our React application will be injected here.

You may also note that there is no client script attached — it is because it is not ready yet. It will be generated by webpack, and then injected to the index file using the webpack plugin called html-webpack-plugin.

Webpack configuration of the client part

An example of client-side webpack configuration.
An example of client-side webpack configuration.
Client-side webpack configuration

For those who are not familiar with configuring webpack, let me shortly explain all the options configured in the above example. First, we set the mode option that tells webpack to use its built-in optimizations.

In the entry section, we configure what the entry point of the bundle is. As you can see, currently, we have only one entry point called client , and it points to the client.js file. Please note that before loading this file, we also load the babel-polyfill library.

Next, in the output section, we can define where the output files will be placed. Here, we set the target folder to build and the file name is [name].js where the [name] is a placeholder that will be replaced by the entry point name (which is just client in our case).

The next section is called module. Here we can configure all the loaders that will be used to transform all the specified file types. In our case, we use babel-loader to transform *.js and *.jsx files.

In the end, we configure plugins and optimizations. The HtmlWebpackPlugin plugin is used to inject the output bundled JavaScript file into the index.html file (we discussed it before). In the optimization section we tell webpack to put all the vendors to (packages imported from node_modules) the separate vendor.js file.

Ok, now we can test our configuration. We can do it by calling the following command (being in the root folder of the project):

Command calling webpack to build the project.
Command calling webpack to build the project.
Call webpack to build the app

This will create the build directory with all the necessary files. If you want to test it, just go into this folder and run some local web server, e.g., on Mac you can run the Python’s Simple Server:

Command running local python web server.
Command running local python web server.
Run some local server to test

Now open your web browser and open the http://localhost:8000 page.

Server-side rendering

Now, it is time to add server-side rendering to our application. Our goal is to move the last part (rendering and placing it into the container) to the server. Thanks to that, the browser will get the index.html file already fulfilled appropriately, and the user won’t have to wait for scripts to be loaded.

Before we start adjusting our configuration, we have to install some dependencies. First, the “dev” one:

Command adding webpack-node-externals package to the project.
Command adding webpack-node-externals package to the project.
Add webpack-node-externals package

Next, the common one for both environments:

Command adding express package to the project.
Command adding express package to the project.
Add express package

I will explain why we need them later.

The Html React component

An example of the Html component that is responsible of rendering scripts and children passed via props.
An example of the Html component that is responsible of rendering scripts and children passed via props.
Html.js

The above component renders more or less the same HTML structure as the index.html. It also obtains two props: children and scripts. The content of the first one is injected into the “app” container. We have to use the dangerouslySetInnerHTML attribute because children contains the HTML markup as a string, and we don’t want it to be escaped.

The script prop is an array of URLs. We map them into a series of script elements. This way, we will attach to our app all the client-side scripts generated by webpack.

If we have the Html.js component created, we can safely remove the index.html file.

Finally: rendering on the server

Ok, please take a look at the server.js file:

An example of server.js file where all the server-side rendering is performed.
An example of server.js file where all the server-side rendering is performed.
server.js

Let’s see what we have got here… First of all, please notice the import of ReactDOMServer from the react-dom/server package. It allows us to render the React components tree on the server. We also import a few other packages, as well as two components we’ve already created (Html and App).

The next thing is to call an express method and assigning its result to the app constant. This way, we initialize the ExpressJS framework. In the next line, we inform express, where our static files (like scripts generated by webpack) are placed.

In the following line (the app.get call), we start the most exciting part of our example, but we will discuss in a moment. First, let’s take a look at the last line — by calling the listen method of the app object we start the whole application — it begins to listen on port 3000.

Ok, now let’s get back to the app.get call mentioned above. This way, we can handle GET requests that are sent by the web browser. As a first parameter of the call, we pass the address we want to handle. In our case is an asterisk — it means that the callback function passed as a second parameter of the call will be invoked for every GET request.

The callback function takes two parameters: req (request object) and res (response object). We will use the second of them at the end of the method, but first, a few things happen.

First of all, we define an array containing paths to scripts generated by webpack. We will pass it to the Html component — you may remember that there, we map this it into a series of script tags.

Secondly, we call the renderToString method of the ReactDOMServer object. We pass the App component as a parameter of this method. Please note what text we assign to its initialText attribute. This way, we render the whole React app to string and assign it to the appMarkup constant.

Thirdly, we call another method of the ReactDOMServer object — renderToStaticMarkup. It works almost the same as the renderToString method. The only distinction is that renderToStaticMarkup omits all the HTML attributes React adds to the DOM during rendering. We pass the Html component as a parameter of the call. It takes children and scripts array through its attributes. This way we wrap the React component tree we have just rendered by the HTML body and save it as a string in the html constant.

At last, we just call the send method of the res object. By doing this, we send the fully rendered app to the browser.

Changes to webpack config

The final version of the webpack configuration for both the server and the client.
The final version of the webpack configuration for both the server and the client.
webpack.config.js — final version

To cut the long story short, let’ focus only on the most important things here.

In the example above, we have two configurations now: clientConfig for the client and serverConfig for the server. Moreover, we have the common object that contains a mutual part of the configuration. Please note how we export our config at the end of the file — using an array, we can pass more than one setting to webpack.

Regarding the client part of the config, almost nothing has changed — the only thing is that using HtmlWebpackPlugin is not necessary anymore. Additionally, the node property has been added — by this option, we can configure whether to polyfill or mock specific Node.js globals and modules. Please also notice how name and target properties define the purpose of the output.

Now, let’s take a look at the server part of the configuration. As you can see, we share the loaders config with the client configuration (...common). The target property points to “node” this time. We also added the externals option defining all the dependencies that are necessary during bundling but not needed in the output file (thanks to the webpack-node-externals library we don't have to do it on our own). Of course, the most important are two properties: entry and output. Besides using babel polyfills, we set the entry point to the server.js file we had just defined a few minutes ago. The output bundle will be placed in the build directory.

Sharing initial state

Command to run building the app and running it instantly.
Command to run building the app and running it instantly.
Build and run the app

The above command will build both the server and the client code, place it into the build folder, and run the app. Now, open the http://localhost:3000 address in your web browser and see the result.

It is not exactly what we wanted to achieve, right? When the browser loads our application, it renders the server-side version first (you may see the “rendered on the server” text for milliseconds) and then runs the client-side code that replaces the original text by “rendered on the client.”

To fix it, we have to share the initial state of our app between server and client. To do so, let’s modify the Html.js component code like shown below:

Modified Html component that adds the initial state to the window object.
Modified Html component that adds the initial state to the window object.
Modified Html.js component

Two things have been added here: first of all, we have passed additional property through the props — initialState; secondly, we have added a script that converts it into JSON format and assigns it to the window.APP_STATE property.

Of course, the script we have just added will be called after the page is rendered in the browser, so we don’t have to be afraid that the window object is not defined.

Now, it’s time to modify the server.js file too:

Modified server.js file that creates shared initial state, uses it and pass it to the Html component while rendering.
Modified server.js file that creates shared initial state, uses it and pass it to the Html component while rendering.
Modified server.js file

Let’s take a quick glance at what had changed here… Firstly, the initialState object was introduced. Secondly, we passed it to both App.js and Html.js components.

You may wonder what the below statement means:

An example of passing attributes using the spread operator.
An example of passing attributes using the spread operator.
Passing attributes using the spread operator

It is just usage of the spread operator to pass all the properties of the initialState object as attributes of the App.js component. So the above statement is equal to the one shown below:

An example of passing attributes explicitly.
An example of passing attributes explicitly.
Passing attributes explicitly

The last thing we did is to pass the whole initialState object to the Html.js component. This way it will be added to the window.APP_STATE property, as described before.

There is one last thing to do in our project. We have to get the value of the initial state in the client code. We can do this by modifying the client.js file:

Modified client.js file that uses passed initial state and hydrates the app.
Modified client.js file that uses passed initial state and hydrates the app.
Modified client.js file

As we already know, this code will be invoked in the browser, so we can be sure that the window object is available. We expect here it has the APP_STATE property defined and using the spread operator we pass all of its properties as attributes of the App.js component.

Please note the second distinction we have introduced here. We replaced the render method of the ReactDOM object by the call of the hydrate method. If we pre-render React code on the server and want to connect it with the client-side React code, we have to use the hydrate method — otherwise we will get an error in the console.

Ok, all set now! You can test it once again by calling webpack && node ./build/server.js and opening http://localhost:3000 in the browser. This time you will see the “rendered on the server” text as expected.

Summary

The example we have discussed today is available in my GitHub repository. I encourage you to clone it and play with it on your own.

In the next issue of the “Server-Side Rendering in React” series, I plan to explain how to handle Redux in our SSR example.


P.S. This post is a part of a series of articles about Server-Side Rendering using React. Please see the list of all articles of the series below:

A note from JavaScript In Plain English:

JavaScript in Plain English

Learn the web's most important programming language.

Bartłomiej Dybowski

Written by

Senior Frontend Developer at AppUnite / Blogger / JavaScript / TypeScript / React / Vue

JavaScript in Plain English

Learn the web's most important programming language.

More From Medium

More from JavaScript in Plain English

More from JavaScript in Plain English

More from JavaScript in Plain English

32 funny Code Comments that people actually wrote

10.4K

More from JavaScript in Plain English

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade