Accessing Heroku config variables client-side in a Rails/React application

Hybrid Rails and React projects are a pretty common thing these days. So much so that Yarn and Webpack have been better integrated into Rails 5.1.

If you’re deploying apps like this to Heroku, you may have found yourself needing access to your Heroku config variables from within your bundled Javascript. I know I have!

With Heroku, accessing config vars in a Rails application server-side is pretty straightforward. All you have to do is use ENV[variable] like normal. But for hybrid apps using Webpack, accessing these variables client-side requires a little extra setup.

Here’s how you can give your React code the same access to these Heroku config variables as for Rails:

First, you need to make sure that Heroku recognizes that you’re using Node as well as Ruby. If you’ve pushed your project to your Heroku app, chances are Heroku has automatically recognized your app as a Rails app, but not as a Node app as well. You’ll probably have to add the Node.js buildpack in addition to the Ruby buildpack that may already be there. (For more information on how to set up Heroku buildpacks, go here.) In my experience it’s best if the Node buildback is listed before the Ruby buildpack for more consistent builds.

Next, you need to set up Webpack to handle different environments. I’ve found it nice to just have separate Webpack config files for each environment that are used according to the scripts specified in your package.json file:

/* script commands have been paired down for clarity. 
Use any flags you need. */
"scripts": {
"dev": "webpack --config webpack.config-dev.js",
"test": "webpack --config webpack.config-test.js",
"webpack:deploy": "webpack --config webpack.config-prod.js"
}

In your respective Webpack config files, you’ll define your global variables for each environment using Webpack’s Define plugin. For example, in my development config file, I’ve set a few static variables:

/* in webpack.config-dev.js */
...,
plugins: [
new webpack.DefinePlugin({
NODE_ENV: JSON.stringify('development'),
API_HOST: JSON.stringify('http://localhost:3000')
})
],
...

Note that, per Webpack’s documentation, any values provided must include quotes, even strings themselves. You can do this either by wrapping the value in alternate quotes, like ‘“development”’, or by using JSON.stringify('development'). I prefer to use the latter because I think it looks more intentional.

This will make these variables available globally to whatever javascript you’re bundling with Webpack. I can now use my API_URL constant freely, without having to add any other messy conditionals:

fetch(API_HOST + '/users', {    
method: 'GET',
headers: headers
})

So, for production… if you’re using the Heroku Node.js buildpack, Heroku will expose any config variables you set in the object process.env. For instance, if I have a Heroku config variable NODE_ENV set to true, I can get this value through process.env.NODE_ENV.

Your Webpack config files will have access to the process.env object, but the rest of your bundle will not, unless you specify them through the Define plugin, like so:

/* in webpack.config-prod.js */
...,
plugins: [
new webpack.DefinePlugin({
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
API_HOST: JSON.stringify(process.env.API_HOST)
})
],
...

Don’t forget to stringify these variables as well!

And that’s really it. I admittedly put off making my javascript constants dynamic for too long, just resetting the variables as I needed to. But when the time came to have multiple Heroku environments for staging, production, etc., doing this became too much of a chore, and it raised the potential for some really serious mistakes. Don’t be like me! Set up your javascript bundles from the get-go to handle multiple environments and dynamic configurations.

Show your support

Clapping shows how much you appreciated Eleni Chappen’s story.