How to Handle Runtime Environment Variables with React

Learn how to easily configure a React application so that it uses environment-agnostic configuration and thus can be built once and deployed anywhere.

Hasni Arif
4 min readJun 23, 2021

Front-end applications may need to consume environment variables (like APIs URLs). As these variables could change between environments, you’ll need to configure your app to call the right ones in each of these environments.

There are two main approaches that you can consider to configure a client-side rendered app that uses environment-specific settings.

The environment-specific build

The first approach we would tend to think about would be to have an environment-specific build. By that, I mean using the CI server to generate and deploy custom builds for each environment.

It’s totally fine and it will actually do the job. The main problem of this approach is that if we compile all of these values into the bundle, a rebuild would be required for every promotion (ex: pre-production to production). This is definitely not ideal and it would be much better if we could have an environment-agnostic build.

Build once, deploy anywhere

As you may guess, the second approach is about having an environment-agnostic configuration. In this case, we are referencing the relevant configuration data at runtime so that, unlike the first approach above, the same code can be deployed to all environments. Therefore, the build stage is only about creating a bundle that could run anywhere, which is more compliant with the Twelve-Factor App as well.

So as it is preferable to deploy the same code to all environments, the runtime configuration approach is the one that we’ll consider here.

The React-related problem

Now the difficulty stems from the fact that your React app is probably built with Webpack and there is a difference between the way Webpack handles environment variable updates, compared to the way Node.js does it.

But Webpack is a Node.js tool, isn’t it? Yes, it is, and during the build phase, it has full access to the Node.js runtime which means it is able to use the FileSystem API and environment variables to properly inline those values in the code… but at runtime only. That’s why your app can actually read environment variables values on development mode using the process.env variable.

The problem is that on production mode, the Webpack output is just a static HTML/CSS/Javascript bundle. It means that our application, being static, can’t detect new environment variables. If they change between environments, and if they need to be embedded in the static bundle, then the app needs to be re-built for that specific environment (and this is precisely what we want to avoid, right?)

So how can we customize the configuration of React.js apps at runtime without having to be aware of the environment?

The implementation

One of the most straightforward solution that I ended up with to keep the entire application build intact was to use a configuration file (env.js) that the app will import in a <script> tag inside the head section of the index.html file.

This script will be responsible for injecting global variables into the app with every variable value being just a placeholder for now. If you’re using a create-react-app (CRA) folder structure, make sure to place this script in the public folder.

// public/env.js
window.env = {
KIBANA_URL: '__KIBANA_URL__',
};

Import this script in your index.html file.

<! –– Example using CRA -->
<script src="%PUBLIC_URL%/env.js"></script>

Then, before serving the app, by running a simple shell script that uses the sed command, the placeholders will be replaced by the actual values of each environment variable.

#!/bin/bashsed -i 's#KIBANA_URL#'"$KIBANA_URL"'#g' env.jsexec "$@"

Finally, to read the values of the variables in your app, you can do something like this:

const KIBANA_URL = process.env.NODE_ENV === 'production' ? window.env.KIBANA_URL : process.env.REACT_APP_KIBANA_URL;

Docker

If you are using a Docker container, just make sure that it has access to all your environment variables and that it runs the script above before serving the application.

...ENTRYPOINT ["./entrypoint.sh"]CMD ["serve", "-s", ".", "-p", "3000"]

And that's how you finally get a "dynamic" bundle and the "build once, deploy anywhere" approach that we want.

⚠️ Do NOT inject secret keys into your bundle

One more thing that is important to consider when dealing with environment variables in React (or any other frontend library) is that you must never inject secret keys into your bundle. Remember that all these values are still visible by inspecting your app's files. Nothing is hidden by your frontend application.

Thanks for reading me and stay tuned, more articles are coming soon ✌️

--

--

Hasni Arif

Software Engineer in Paris, France 🇫🇷 React.js & GraphQL Enthusiast