Serving React and Flux with Hapi and Webpack

In my previous two posts, I explained the process of connecting React to a Flux store and getting external data from an API into Flux. Using the Facebook Flux examples’ model of manually building an index.html file is great for learning, but now I want to serve my app according to a standard 12-Factor compliant model. This will allow me to publish my app to a server or Docker container for hosting. Since I’m familiar with Hapi.js, I will use it as my HTTP server and Webpack to coordinate the packaging of my JavaScript assets.

A good visualization of this architecture strategy is provided by Spike Brehm

One important note, it is becoming more and more popular to render React and Flux on the server to improve performance and user experience by serving “compiled” HTML and app state straight to the client. It’s also becoming popular to use isomorphic JavaScript to organize and combine server-side and client-side codebases. While server-side Flux rendering and isomorphism is great, it adds a bit more complexity than I’d like to tackle right now in this todo example. Consequently, I’m going to choose a first-step strategy of rendering a React template on the server that delivers the SEO-compatible content as well as the state-managing React and Flux components to the client.

First, I’m going to initialize the app and install the Node.js dependencies. If you don’t have Node.js installed, head over here to get that done.

# Initialize a new Node.js app
npm init
# Install Hapi, React, Flux, and dependencies
npm install --save hapi react react-dom flux hapi-react-views vision inert object-assign dateformat superagent babel-register babel-preset-es2015 babel-preset-react

*Update: Added babel to the list of dependencies since hapi-react-views no longer uses node-jsx but requires you to register Babel in the server.js file. The upside to this is you can start using ES6 and ES7 features anywhere in the server-side code. For the client side, check out Migrating to ES6 with Babel and ESLint.

Also, Babel 6 requires a .babelrc file to be placed at the root of the project declaring the configuration:

Next, I’ll add a basic Hapi.js server file. This will provide a few things:

  • An HTTP server accessible at http://localhost:8000 to serve the web app;
  • A view engine using the Hapi plugin vision and the hapi-react-views package to render the React/JSX components to a string before sending it to the client;
  • A static directory route using the Hapi plugin inert to serve client-side assets like CSS, JS, and IMG; and
  • A main app route that will serve a Default.jsx view template.

Here’s the completed server.js file that I’ll put in my project’s root directory:

To start it up, I’ll simply run:

node server

For the Default.jsx template that I’m placing in my views directory, I’ll create a new React component and copy over my existing index.html content. One important note, I had to alter some of the tags when importing over to a JSX template. This included adding closing tags to the meta and link tags, changing the charset property to charSet, and changing my inline style string on the body tag to an embedded JavaScript object with camel-cased properties. Here is the completed template:

Now I have the ability to dynamically set my script names, page title, and more with simple JavaScript if the need arises. In addition, I could embed any other components that need to be SEO-compliant; but for this example, I’m going to move on to better things.

After setting up the server and template, it’s time to automate the bundling of the client-side assets. Webpack provides a holistic approach by automatically following an entry file to every dependency that it needs. It then bundles all of the dependencies into one file. In addition, I noticed Webpack bundles about twice as fast as Browserify (based only on my testing in this project).

Another item of interest is that Webpack is often used in conjunction with a “hot-loading” development environment which automatically updates your code without refreshing the browser. In order to add hot-loading to this Hapi implementation, I’d need to take Kevin Old’s approach of attaching the webpack-dev-server to the public assets path that I’m serving. For now, I’ll resign to the fact that I’ll be refreshing my browser after changes like other web projects. Update: I recently added Webpack’s BrowserSync plugin which can help boost productivity.

First, I’m going to move all my client-side assets into a new assets directory off the root. Second, I’ll install the development dependencies I’ll be using:

npm install --save-dev nodemon webpack babel-core babel-loader

I’ve added nodemon to the mix because it’s a simple way to watch files and restart the server with minimal configuration. By default, nodemon watches all files in the current directory and runs npm start. Since Webpack will be bundling and watching my client assets, I don’t need nodemon to restart the server when those change. Consequently, here’s the nodemon.json file I’ll place in the root directory to configure all this:

The restartable option allows me to enter “rs” after nodemon is running to quickly have it restart (versus [Ctrl]+[C], [up], [enter]). The ignore array will have nodemon ignore the .git, node_modules, and assets folders.

Next, I’ll create the following webpack.config.js Webpack configuration file in the root directory:

The main parts of this file are the following:

  • The entry — Webpack looks at the Index.jsx file to start bundling and tracing dependencies,
  • The output — defines the location of the bundled file, and
  • The loaders — it needs to process JSX file transformations with babel-loader.

I also edited my package.json file to add the following script:

"scripts": {
"start": "node server.js",
"build": "webpack -p --progress",
"dev": "webpack --progress --color --watch & nodemon"

This allows me to quickly build the bundle and start the server during development with the following command:

npm run dev

I can also kick of a bundle of a production version of my assets and start the server with these commands:

npm run build
npm start

Note that I won’t be able to simply run the webpack command directly since I opted to install it locally. However, using an NPM script automatically sources the ./node_modules/.bin folder which makes the webpack command available in NPM scripts.

With Webpack and Hapi.js setup to bundle and serve, the Todo app can now be plugged into a deployment pipeline. By standardizing the app with a published port and providing build and run scripts, it’s even easier for an ops team or the continuous integration/delivery system to manage subsequent testing, packaging, and releasing. The next step might be configuring and packaging the app in a docker container for local development, remote testing, and deployment.

The full code of this example of serving React and Flux with Hapi and Webpack can be found here on GitHub.

If you found this post helpful, please let me know by selecting the heart below to recommend it.

Husband, Full-Stack Developer, Recovering Sysadmin, Entrepreneur, 12-Factor/DevOps Advocate, #CavDad. Using @reactjs, #redux, and @reactnative.

Husband, Full-Stack Developer, Recovering Sysadmin, Entrepreneur, 12-Factor/DevOps Advocate, #CavDad. Using @reactjs, #redux, and @reactnative.