Using environment variables in a React application

In this article we’ll see how to use environment variables in a React application. Specifically, we’ll look at two kinds of environments variables:

  • Build time variables: these are variables that are provided when the application is compiled (typically when npm run build is executed). In our example, we’ll pass to the application the git hash of the code when it was built, and an additional arbitrary value.
  • Run time variables: these variables are provided when the application runs, usually these variables differ depending on the environments (development, staging or production). In our example we’ll pass an API URL from the server to the front end application.

Skipping ahead

Build Time Variables

This is documented on the official create-react-app website: https://create-react-app.dev/docs/adding-custom-environment-variables/, but let’s break it down step by step.

Let’s look at two ways of passing an environment variable to a React application:

Using a .env file

At the root of the project, create a .env file with the following content:

REACT_APP_MY_ENV=Some value

In the application, this value can simply be read as such:

const { REACT_APP_MY_ENV } = process.env;

Assuming the application was created with create-react-app, this will work out of the box, with no additional work.

If the application was not created with create-react-app, add the dotenv package to the project.

$ npm i dotenv

And load the configuration when running node:

// package.json"scripts": {
"start": "node -r dotenv/config src/index.js"
}

When using create-react-app, the variable needs to start with REACT_APP_ otherwise this won’t work, and it’s mostly for security reasons. It’s a good idea to keep the same convention even when not using create-react-app, it just makes it really obvious where this value is coming from.

Using temporary environment variables

The other way to do this is to use a temporary environment variable. This is convenient when we want to use a dynamic value for the environment variable, such as a date/time or a git hash. In our example, we’ll use the git hash.

The way to use a temporary environment variable is to add it to the “start” command:

// package.json"scripts": {
"start": "REACT_APP_GIT_HASH=\"$(git rev-parse HEAD)\" react-scripts start"
}

Now this value is available in the application, and it can be printed to the console, or rendered in a component:

const { REACT_APP_GIT_HASH } = process.env;console.log(`Version ${REACT_APP_GIT_HASH}`);
// Version 8e8bd178bd91adb33b7eb5cb1b8220a079a87e65
function MyComponent() {
return <div>{ REACT_APP_GIT_HASH }</div>; // renders the hash
}

Again, the same naming convention applies here, the variable needs to start with REACT_APP_.

Keep in mind that when building the application by running npm build , these variables will be embedded in the build so don’t put anything secret in there! A git hash is fine, an API key or a password is not.

Since the values are embedded, they are accessible at runtime, there is no need to provide a .env file or a special command to the package after it is built, so there is no additional work to be done for these values to carry from a local development version to a production build.

$ npm run build
$ serve -s build
// App.js
const { REACT_APP_GIT_HASH, REACT_APP_MY_ENV } = process.env;
console.log(REACT_APP_GIT_HASH); // 8e8bd178bd91adb3.....
console.log(REACT_APP_MY_ENV); // Some value
function App() {
return (
<>
<div>Git Hash: {REACT_APP_GIT_HASH}</div>
<div>My Env: {REACT_APP_MY_ENV}</div>
</>
);
}

It just works! dd

Recap:

To pass an environment variable to the application:

  • Prefix the variable name with REACT_APP_
  • The value can be provided it on the command line
    "REACT_APP_MY_VAR=foo react-scripts start"
  • The value can be provided in a .env file
    REACT_APP_OTHER_VAR=wombat
  • Read it from process.env
    const { REACT_APP_MY_VAR, REACT_APP_OTHER_VAR } = process.env;
  • No sensitive information

Runtime Variables

Say we need to provide to our application a value that will change depending where the application runs, like an api url. We can’t read it from the JavaScript application as it is executed on the client. So we’re going to read the variables on the server, and pass them as parameters to the JavaScript application.

First, let’s add the variable in the development environment so it’s availble when we run npm start. This doesn’t change one bit, we’re just going to add it in the .env file:

// .env
REACT_APP_API_URL=http://localhost:3000/api

And that’s it, this variable is now available in the code:

const { REACT_APP_API_URL } = process.env;

That takes care of having the variable available in development mode.

Now when building for production, we need some sort of server processing to inject the value into the template. So the first step is to edit the index.html:

// public/index.html<body>  //...

<script>
window.API_URL = "\<\%= API_URL \%\>";
</script>
</body>

When serving the app, the API_URL string will be replaced by whatever value we want. Notice how the characters are escaped, this is because Webpack uses the same kind of tags, and we don’t want it to process this instruction.

Now we need to write the server that will substitute the value at runtime, here we’re going to use a simple Express app:

// backend/index.jsconst express = require('express');
const path = require('path');
const app = express();
const port = 4000;
// where ever the built package is
const buildFolder = '../frontend/build';
// load the value in the server
const { API_URL } = process.env;
// treat the index.html as a template and substitute the value
// at runtime
app.set('views', path.join(__dirname, buildFolder));
app.engine('html', require('ejs').renderFile);
app.use(
'/static',
express.static(path.join(__dirname, `${buildFolder}/static`)),
);
app.get('/', function(req, res) {
res.render('index.html', { API_URL });
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

And in the same folder as the server application, we need a .env

API_URL=https://myserver/api

Now we have the environment variable REACT_APP_API_URL when in development, and we have the API_URL when serving the built package. We need to differentiate the two in our frontend code. Lucky for us, there is a variable that lets us know which environment we’re in: process.env.NODE_ENV :

// App.jsconst API_URL = NODE_ENV === 'development' ? process.env.REACT_APP_API_URL : window.API_URL;

This says “If we’re in development, use REACT_APP_API_URL, otherwise us window.API_URL”. And that’s it!

You can clone the code here:

And run it in development mode:

$ cd frontend
$ npm start
$ open http://localhost:3000

You will see the following:

Screenshot of home page in development

And compare it to the built version:

$ npm run build
$ cd ../backend
$ npm start
$ open http://localhost:4000

You will see the following:

Screenshot of home page in production

Note how the value of the API variable changed to reflect what’s in the .env file!

One last word, depending what you read, you’ll see conflicting points of view whether or not the .env file should be checked in.

The create-react-app website says:

.env files should be checked into source control (with the exclusion of .env*.local).

The dotenv website says:

We strongly recommend against committing your .env file to version control.

The twelve factor app website gives a much lengthier explanation:

Another approach to config is the use of config files which are not checked into revision control, such as config/database.yml in Rails. This is a huge improvement over using constants which are checked into the code repo, but still has weaknesses: it’s easy to mistakenly check in a config file to the repo; there is a tendency for config files to be scattered about in different places and different formats, making it hard to see and manage all the config in one place. Further, these formats tend to be language- or framework-specific.

The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.

We can come to the following conclusion

  • If the file is a ‘config file’ containing constants not likely to change from one server to another or from one build to the next (like a timezone, a locale or specific content, like the site name), then it’s OK to check it in.
  • If the file contains application configuration, like a database password, an API key, any value that needs to change without having to rebuild the application or from server to server, then it’s better not to check it in and use environment variables. Check the dotenv config on how to manage multiple env files.

Use your better judgement, and remember this: environment variables provided as REACT_APP_ will be embedded in the application so they should not contain anything sensitive.

Download the full code here: https://github.com/arnaudNYC/react-app-env-vars/

Previously Paris and Geneva, currently New York. Can also be found at https://dev.to/arnaud