Using Environment Variables in a React App
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.
- Firstly, it helps us ensure that configuration isn't tied to our codebase directly. This separation of concerns is a key goal of Twelve-Factor Apps and helps us avoid the need to have configuration files for each environment in our codebase.
- Secondly, it allows us to manage environment specific configuration at the infrastructure layer. We deploy identical code to all environments, and the environment lets us set up the application differently for production and non-production.
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:
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:
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.
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/
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.