Hot Module Replacement with React and Express

Phil Parsons

Tagged: , ,

As promised in the last article we will now run through how to set up a development environment that will automatically rebuild our code and restart the application server. Once the bundle has been rebuilt we also want to refresh the browser so that we can see the new changes. One approach to this is to have the Webpack Development Server (WDS) reload the page for us but a better approach is to hot replace the updated module so that we do not lose the state in our Redux store. In this article we will run through the set up of a WDS that will enable us to use Hot Module Replacement (HMR) integrating seamlessly with the existing Express application server.

This post is part of the Developing for a modern web with React.js series. If you’d like to be notified of new posts in this series you can become a free member. If you don’t want to complete the previous articles before working through this tutorial you can download the code from Github.

To start, install the webpack-dev-server package and the react-hot-loader for Webpack.

Before editing any configuration we need to stop and think about the future of the application and how it will run in production. Many settings for how our server will run in development will differ from how it will run in production and these differences need to be managed with the use of environments. In this article we will focus on creating the configuration for our development environment only.

Setting up a Webpack Development Server

We are going to create a script to configure and start a WDS programatically. In this script we will import and build upon the standard configuration we already have in ./webpack.config.js but to do this we need to make some changes to that file so that the configuration is more easily shared.

The configuration has been refactored so that certain parts are easier to use from other modules. The syntax has changed to ES2015 but the default module export is the same as it was previously. Now this configuration is refactored we can create the script to build and start the WDS. Write and save the below code to ./webpack.development.js.

In this script we import the webpack and webpack-dev-server modules along with the existing webpack.config. A new configuration for the WDS is created using existing configuration options from the client setup with some additions. The react-hot-loader and HotModuleReplacementPlugin are added to enable HMR. The ExtractTextPlugin is not used because we want the styles to also be hot replaced when we update them. Two additional entry points have been added; webpack/hot/only-dev-server adds the HMR client code for handling the module updates and webpack-dev-server/client?${HOST_URI} adds the code to communicate with the WDS about HMR updates. We must pass the host URI of the development server to this entry point as we still want to view the index page served from Express. Lastly, in the output section the publicPath setting is required to tell webpack where to load the updated modules and again we must provide the host URI for the WDS because we are using Express to serve the page.

A new Webpack compiler is created with the devConfig and used to create a new WebpackDevServer. We add some configuration that is specific to the WDS but for now we just add the hot option to enable HMR. The WDS is started and listens at http://localhost:8080.

The WDS builds and serves our client application bundle from memory but we also need to update the generated code for the Express application server. To do this we will programatically start a secondary Webpack compiler to build the server components when code changes occur. Let’s add that now in the same file before moving on. In addition now is a good time to add some cleaner formatting for the stats on each build to prevent clogging up the terminal window.

Go ahead and start the WDS!

Configure Express to serve Webpack assets

The dev server is up and running but our main layout in express expects to serve our bundle files statically from the /dist directory. We need to change this so that when we run the server in the development environment the JavaScript bundle is served from the WDS and the extracted CSS bundle is not included. To do this we need to create a script to start the application server with different settings for each environment. To do this we will edit ./server/index.js to export the application server and not start it. We also need to update the path to our views as we will be starting the server from the root of the project folder.

Next create a new file ./index.js and add the below code.

In this code the server module (app) is imported before a check to see if the environment variable NODE_ENV is set to ‘development’. If it is we configure options based on how we want to run the application in the development environment. The port and host options are set in the configuration for starting the server and the assetPath and isDevelopment flag are set in the server locals. The server locals are settings that are global to the application and can be accessed from our templates. Update the main layout template ./server/views/layouts/main.handlebars to use these variables for serving the assets from the WDS.

We haven’t configured anything for additional environments such as production just yet so running the application without setting NODE_ENV to development will result in an error. Open a new terminal window and start the application server to view the changes in the browser at http://localhost:3000.

If you open the console in your browsers development tools you should see a log message from the HMR code about waiting for a signal from the WDS and another stating that the WDS has HMR enabled. Great!

Add a couple of messages to the chat using the MessageEntryBox before making a small change to the component ./client/components/message-entry-box/index.js to add a placeholder in the textarea.

Save the file and check back in the browser. You should have notifications in the console about the module being updated and the textarea should show the new placeholder. The update happens without affecting the state of the application and the messages previously entered should still be visible.

Use nodemon to reload the server

The development environment is starting to take shape but we still have to restart the Express server if any of the server code changes (including the server generated components). Nodemon is a utility that is able to watch our server process and restart it anytime the server code changes. Install the nodemon package globally with the -g options.

Once installed stop the express server if it is already running and restart it with nodemon.

Now when Webpack updates the components or any of the server code including the handlebars templates change nodemon will restart the server for us. Try it for yourself by changing the placeholder text in the MessageEntryBox component.

Use NPM scripts

The options required to run these servers are getting a bit verbose and difficult to remember. We can use NPM scripts to simplify the process and make our life easier. Add two scripts to the scripts section of ./package.json to start each development server.

Now we can run the servers with the commands npm run webpack-dev and npm run start-dev.

This post is part of the Developing for a modern web with React.js series. If you enjoyed the tutorial and would like to follow along to future posts please sign up to become a free member.

It's only fair to share...Tweet about this on TwitterShare on FacebookShare on Google+Buffer this pageShare on LinkedInPin on Pinterest

/ 9 Articles

Phil Parsons


  1. Thomas Jones

    NPM “start-dev” script for Windows should be “set NODE_ENV=development&& nodemon –exec babel-node index.js -e js,handlebars”


    • Phil Parsons

      Thanks Thomas, I’ll do my best to cover both environments for future articles.


  2. robbinjanssen

    Great articles, am curious on how you are going to handle routing in this directory structure. Do you need to provide a generated version of your routes (which will contain components etc) to the server as well?


    • Phil Parsons

      Thanks. The chat window being built currently will only require a single route with an optional chat id to re-load an in progress chat. Once we move on to build the admin interface there will be additional Redux router routes that will map 1:1 with routes in express. You’re essentially right, the route handling components will be generated as individual chunks, required and rendered in the corresponding route in Express which will load the initial data state from RethnkDB.


  3. Martin Gar

    I have more than 15 years of object oriented programming in PHP, Java, C# and C++ and I feel like a stupid monkey when reading this series.

    JavaScript is such a huge unknown to me, it is so difficult for me to grasp. So many standards, the code seems really strange, but seems to do what it is supposed to do.

    Thank you very much for the series.


  4. Dan

    How do we get the original webpack command working to dump the assets into the dist folder?


    • Phil Parsons

      At this point you could run babel on webpack config first something along the lines of babel webpack.config.js –out-file webpack.es5.config.js && webpack –config webpack.es5.config.js or you could add a script that does it programmatically.


  5. Pali

    babel-node is not recommended for production


    • Phil Parsons

      Very true. The code would need to be built on a CI server before being deployed to production servers.


  6. publicJorn

    Some time passed since this was written, but it still helps me a lot!
    In order to get HMR working now, make sure to fetch a previous version of react-hot-loader with npm:

    npm i react-hot-loader@1.3.0 -D

    The new version 3 is still in beta and not production ready. Also, it works differently, so the above doesn’t apply anymore.


Trackbacks & Pingbacks

  1. Hot Module Replacement with React and Express – Spraso – Dev Environment

Leave a Comment

Your email address will never be published or shared and required fields are marked with an asterisk (*).