Using Environment Variables in a React App

Published on

Using environment variables for environment specific configuration has been a technique in backend development for a long time. It skyrocketed in popularity when the Twelve-Factor Apps guide was initially published by Heroku in 2011 and has been a standard way of managing configuration ever since.

The purpose of moving configuration into environment variables is two-fold.

So, how do we apply this technique to a React application?

In a pre-built frontend application we can't access environment variables configured on our server. Instead, we set up the environment specific configuration at build time using process.env and use our CI/CD pipeline's tooling to set up each environment.

For a simple example, let's a new application with create-react-app and inject an environment variable.

> npx create-react-app env-test
> cd env-test

Replace App.js with the following, note the use of process.env.REACT_APP_ENVTEST to access our environment variable. In order to access environment variables they must be prefixed with REACT_APP. This stops us accidentally leaking private environment variables into our application at build time.

import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <p>
          {process.env.REACT_APP_ENVTEST}
        </p>
      </header>
    </div>
  );
}

export default App;

We'll run our server locally and include the REACT_APP_ENVTEST variable on the command line. You could also export this into your environment or use one of the many tools that let you configure environment variables when you run the server.

> REACT_APP_ENVTEST=abc123 npm start

Open the application in your browser and you should see this:

Running react application with abc123 in the center of the page

To demonstrate how this works at build time, build the application with a different value for the environment variable:

> REACT_APP_ENVTEST=different-value npm run build

Move into that directory and use Python's handy http.server to serve those built files (you can also install and use serve, the output of the build command has instructions for how to do that).

> cd build
> python3 -m http.server

The http.server runs on port 8000, so head to http://localhost:8000 and you should see your application running with the new value:

Running react application with different-value in the center of the page

You can follow this pattern to build your application for each environment that you're deploying to. Use your build tooling to specify the configuration. This can be achieved easily with Github Actions by specifying a different configuration in your workflow for each environment specific build.

Create a new .github/workflows/build.yml file with steps to build the application and deploy to Github Pages:

---
name: Build our environment variable test application

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build
        env:
            REACT_APP_ENVTEST: 'built-with-github-actions'

      - name: Deploy to Github Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./build

This will build and deploy your application with "built-with-github-actions" as the configured environment variable. We're getting closer, but we still have the configuration directly committed to our codebase.

You can use build secrets to keep the configuration out of your build.yml file completely. Do this by configuring an environment in the settings of your application called envtest-prod and adding a REACT_APP_ENVTEST secret in that environment.

Screenshot of environment configured on Github

Once that's done, update your build.yml file to use the environment.

---
name: Build our environment variable test application

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    environment: envtest-prod  # add environment

    steps:
      - uses: actions/checkout@v2

      - name: Install dependencies
        run: npm install

      - name: Build project
        run: npm run build
        env:
            REACT_APP_ENVTEST: ${{ secrets.REACT_APP_ENVTEST }}  # use secret configured in environment

      - name: Deploy to Github Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./build

You can see this in action with my sample repo and hosted on Github Pages: https://sesh.github.io/post-envtest/

React application with this-is-a-production-secret displayed

It's important to note that even though we're using Github's Secrets mechanism, these values will no longer remain secret once they are included in your compiled application bundle. This technique is fine for variables that need to change between environments. If you need to keep a configuration value secret you cannot include it in your frontend code.

Since you'll end up building versions of your code specifically for each environment this isn't a true example of the Twelve-Factor App pattern. To get closer to that you'll need to look at ways of injecting the configuration into your application at runtime. More on that later.

While it's not a true Twelve-Factor App it is the simplest way to keep configuration out of your codebase and to create environment specific behaviour in your React application.