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

If we are creating a universal application, we have to handle two cases: rendering on the server and the client. When web browser requests for content, our app should prepare all the necessary data, fill the HTML code with it, and return it to the browser. Then, all the JavaScript files containing the client-side version of our application will be loaded by the browser. Thanks to that, further interaction with our website will be possible.

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 ):

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

Now that we have a component, we can render it. For now, let’s do it only for the client. Please see below the content of the client.js file:

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:

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

The next step is to prepare the index.html file. It will have our client script attached (the browser will load it after the index file is loaded). It will also have a container with the “app” identifier where our app will be injected. To handle all of it will use webpack.

Let’s start by installing all the necessary packages:

Add webpack, babel and html-webpack-plugin

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

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):

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:

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

Now, let’s configure webpack to generate the client’s bundle. To do so, we have to add the webpack.config.js file to our project. Please see below its content:

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):

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:

Run some local server to test

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

Server-side rendering

In the solution I’ve just described above, we simply inject script generated by webpack into the index.html file. When the web browser renders the HTML file, the injected script is loaded and then invoked immediately. This results in React’s component tree translated into DOM elements and placed inside the “app” container.

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:

Add webpack-node-externals package

Next, the common one for both environments:

Add express package

I will explain why we need them later.

The Html React component

In the client only approach, to prepare the index.html file, we’ve used the HtmlWebpackPlugin . Because now we want to render the whole React component tree on the server, we have to use a more sophisticated approach. That’s why I will introduce the Html.js component that will replace the index.html file. Please see below how I’ve implemented it:

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

Now we can move to the most crucial part of this article. Let’s create the server.js file. It will be an entry point of the application and will be invoked on the server based on Node.js. To make it easier to work with its network capabilities, we will use the ExpressJS framework (we added it to our project a few minutes ago).

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

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 server.js file imports React components and use the ES6+ syntax. That's why we have to run it through webpack too. Please see below, the modified webpack.config.js file:

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

Ok, now that we have the app configured for both the server and the client, we can test how it is working. Let’s remove the bulild folder (just to make sure we start with a clean build), and call the following command:

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.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

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:

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:

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

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

I hope that despite the length of this text, I’ve reasonably explained everything. I think it is quite straightforward and I hope you won’t have problems using it for your purposes.

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.