Understanding React deployment

During normal course of development you will use either webpack-dev-server of some http server (eg. express) with webpack as middleware to speed up developement time by using HMR and other tools. Then, comes a time when you need to deploy your application on a testing or production server and ask yourself: “how?”.

Choosing the correct server

First thing you need to figure out is what kind of server / hosting environment will you need. There’s generally one rule — if your app is using server-side rendering you must go with a node server. In other cases you can basically deploy your application on anything you want — apache, nginx, express or host it on a CDN like S3 (Amazon). This article will focus on the later one, we won’t be implementing server-side rendering.

Because of that, our app — from the server point of view — will be just a static website like we used to do back in 1990 before PHP took the world by a storm. Our deployment process will at the very basic level be “put the files on the server”. There are some edge cases which we will cover later on.

Getting your app ready for production

There are multiple things you can tweak and configure when generating a production build, but most important ones are:

minify and uglify your package

This will compress the JS code making it smaller — you can use the UglifyJsPlugin (which does also uglify CSS contrary to what the name would suggest):

module.exports = {
// base config
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}

You can find the extensive list of options you can pass to UglifyJsPlugin on its github page.

configure production environment variable

This will let all the 3rd party libraries know you are building a production package and will result in more optimized package. You can also use this variable inside your own application to change between testing / live APIs etc.

module.exports = {
// base config
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})

]
}

This configuration has to be handled by the libraries you are using, but will save you on size and boost performance in general, as React itself takes use of it. One of the differences you will notice is that React will no longer report on incorrect props being passed to Components — that functionality is now completely stripped out — that’s why you should not rely on propTypes validation as means of securing your app!

disable source map

This one is simple — just make sure your production config does not include devtool option or use cheap-module-source-map, which will still tell you names of the files and lines. This will still add the reference to the map file into your JS so if you upload it the viewer will have some insight into your application, and if you choose not to, he will send an extra request that will end up as 404.

You can achieve all those automatically by running webpack -p to tell webpack that this will be a production build.

After those few changes you should notice that the file generated by webpack is way smaller, allowing you to serve it and start the application faster. You should now be able to just copy the files webpack created for you onto

Common problems after deployment

There are some issues that are common while deploying your first app that I’ve noticed while hanging out at Reactiflux Discord server:

White screen instead of application

This can be called a “general error” — something went terribly wrong and the app failed too start at all. Open your browser developer tools and check for any errors there — there is a high chance that the URL for your bundle is incorrect and the file is not loading at all.

This is common when you are developing locally and then put files on the server, and expect to load them from http://mydomain.com/someDirectory but while developing you used just http://localhost:8080 so the URL to your bundle and CSS files look like

<script src="/bundle.js"></script>

This is easilly solved by updating the references in index.html, but might require setting module.output.publicPath in webpack config in order to update the URL of images and other embeded assets.

React-router not working when typing URL manually

This is a common surprise and is related to the fact that you are using browserHistory in your application, which requires some additional configuration of the server itself. Basically, when you type the URL by hand, by default the server will look for a file with that path, stored on its disk — if not found, it will show a 404 error. What you want to do is internally redirect the request to the index of your application.

You can see the documentation for react-router v3 about this setting (don’t worry, it’s still valid for react-router 4!). Common configuration are:

Apache with mod_rewrite enabled:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

If your application is deployed in a sub directory you must make sure the value of RewriteBase matches the path to that sub directory.

nginx with try_files:

server {
...
location / {
try_files $uri /index.html;
}
}

express:

const express = require('express')
const path = require('path')
const port = process.env.PORT || 8080
const app = express()
// this assumes that all your app files
// `public` directory relative to where your server.js is
app.use(express.static(__dirname + '/public'))

app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})

app.listen(port)
console.log("Server started on port " + port)

Can’t see changes after deploying a new version of the application

This is usually caused by the fact, that the browser holds an in-memory copy of the JavaScript bundle from your previous visit. While this can be worked around by clearing browser cache, you’re not going to expect your visitors to do that — a common way to get around that is using a technique called “cache busting”, which boils down to making sure the path to file you are requesting has changed. This can be achieved by manually changing the name of the .js file and making change to your index.html, but you can also ask webpack to generate an unique name when creating the bundle.

Simplest way to do that is to change the value of output.filename:

module.exports = {
// base config
output: {
filename: "[name].[hash].js"
}
}

This way your build command will generate file with an unique name, eg. bundle.fabb1faf121baf1aca65fb.js (explanation of the placeholders).

AJAX requests no longer working

Most probably you are running into CORS issues — open your browser developer tools and check for failed network requests to verify that.

CORS is a way that browser protect you from leaking data by sending it to domains you are not currently visiting. While this is an important protection mechanism it can be an problem when working with JS applications that contact different APIs on different domains and/or ports.

There are a couple of ways you can get around the issues:

  • make sure your APIs have correctly configured CORS responses
  • make sure your application and API are running on the same protocol, domain, port
  • create a proxy that will run on the same domain as your app, accept all the requests and communicate with the APIs on behalf of your app