Environment variables are used in applications in order to have different behavior for different environments. For example, if an analytics tool is used in an application, it might be useful to send analytic events to different destinations depending on the environment so development and production related data doesn't get mixed together.
This article explains how the dotenv package is the basis for implementing environment variables in JavaScript and how to use environment variables with project bundlers and popular front-end frameworks. The bundlers and frameworks that will be covered in this article are the following:
dotenv
The dotenv package is by far the most popular way of setting environment variables in JavaScript. The dotenv package works by reading environment variables found in different .env
files. The main .env
file usually contains production variables while other .env
files with different suffixes contain variables for other environments. Here are some examples:
.env.development
(used when running an app locally or on a development server).env.staging
(used when running an app on a staging server).env.test
(used when running tests)
Assigning values
The way environment variables are specified in .env
files as follows:
1MY_VARIABLE=aCoolValue2ANOTHER_VARIABLE=yetAnotherValue
All values assigned to environment variables are represented as strings when they are accessed in JavaScript code. That means a variable assigned as MY_VARIABLE=true
will have the value of true
be the string 'true'
in JavaScript.
If spaces are required in the value of an environment variable, then quotes can be used around the value of the environment variable:
1MY_VARIABLE="a cool value"
Reading values
This article won't discuss how to setup dotenv such that it loads the correct environment variables from the .env
files since that is managed by the bundlers and frameworks that are mentioned later on in the article. However, in all the scenarios described in the article, environment variables are added to the process.env
object and can, therefore, be accessed in JavaScript code using the following syntax:
1process.env.MY_VARIABLE
Build time injection
Environment variables are injected into applications at build/compile time, which means that a server will need to be restarted or the project files will need to be re-built in order to see changes made to values in .env
files.
Webpack
When using webpack, environment variables can be set using the DefinePlugin. DefinePlugin allows global variables to be set and made available in JavaScript code. To use DefinePlugin, add it to a webpack.config
file's plugins array:
1const webpack = require("webpack");23module.exports = () => ({4 plugins: [5 new webpack.DefinePlugin({6 "process.env.MY_VALUE": JSON.stringify("aCoolValue")7 })8 ]9});
In the above example, the environment variable can be accessed in JavaScript using process.env.MY_VALUE
. It should be noted that the DefinePlugin does a search and replace of the JavaScript where it will look up any references to process.env.MY_VALUE
and replace it with 'aCoolValue'
. This means that MY_VALUE
isn't a property on the process.env
object and therefore MY_VALUE
cannot be accessed by destructuring the process.env
object:
1const { MY_VALUE } = process.env; // This won't work ❌
Specifying Environments
There are many ways to specify different environments with webpack. A simple way is to pass the path of the webpack.config
file that corresponds with the environment you would like to build:
1{2 "scripts": {3 "build:development": "webpack --config webpack.config.development.js",4 "build:staging": "webpack --config webpack.config.staging.js",5 "build:production": "webpack --config webpack.config.production.js"6 }7}
Resources
Webpack DefinePlugin documentation
Using dotenv in combination with DefinePlugin
Parcel
Parcel uses dotenv to manage environment variables. All that is required is to create .env
files for different environments and then those variables will be made accessible in JavaScript code using the process.env.VARIABLE_NAME
syntax.
Specifying Environments
When running the normal parcel serve command (e.g. parcel index.html
), the environment (specified by the NODE_ENV
environment variable) will be development by default. To specify an environment other than development while using the parcel serve command, you can specify the environment when running the parcel serve command:
1NODE_ENV=staging parcel index.html
When running the parcel build command (e.g. parcel build index.html
), the environment will be production by default. Again, you can specify a specific environment when running the build command:
1NODE_ENV=staging parcel build index.html
Order of priority/inheritance
Environment variables can be taken from multiple .env
files. For example, all environments will inherit environment variables from the main .env
file. If in a development environment, you can inherit values from 4 different files: .env.development.local
, .env.development
, .env.local
, and .env
. If the same variables are found in multiple .env
files, then the value for the environment variable will be taken with the .env
file with the highest priority. Here is the order of priority, from highest to lowest:
.env.${NODE_ENV}.local
.env.${NODE_ENV}
.env.local
.env
Note: The test environment ignores all local .env
files. This is done because the tests should produce the same result for everyone.
Source control
You should commit all your .env
files to source control with the exception of .env*.local
files. The local .env
files should only be used to tweak your app's configurations when running the app locally.
Resources
Parcel dotenv configuration (repo code)
Create React App
Create React App uses both dotenv and webpack's DefinePlugin to manage environment variables. Just like with parcel, environment variables need to be defined in .env
files at the same level as the project's package.json
file.
Naming requirements
Environment variables need to be prefixed by REACT_APP_
in order for the variables to be accessible on the process.env
object in JavaScript.
1REACT_APP_MY_VARIABLE=testing123
Specifying environments
Create React App does not allow to change the value of the NODE_ENV
environment variable. The npm start
command will set the NODE_ENV
to development, the npm test
command will set the NODE_ENV
to test, and the npm run build
command sets the NODE_ENV
to production.
Given that the NODE_ENV
is set for you and that the value for NODE_ENV
is used to reconcile the correct .env
file, the following .env
files can be used:
.env
.env.local
(loaded for all environments except test).env.development
,.env.test
,.env.production
.env.development.local
,.env.test.local
,.env.production.local
Order of priority/inheritance
The order of priority and inheritance is exactly the same as that described for the parcel bundler:
.env.${NODE_ENV}.local
.env.${NODE_ENV}
.env.local
.env
Additional environments
To use environment variables for environments other than development, test, and production, you can create additional .env
files and load the correct .env
file using env-cmd.
To take a staging environment as an example:
- Create a
.env.staging
file and add environment variables to the file - Add env-cmd as a project dependency (
npm install env-cmd --save
) - Create script commands for the
staging
environment
1{2 "scripts": {3 "start:staging": "env-cmd .env.staging npm start",4 "build:staging": "env-cmd .env.staging npm run build"5 }6}
- Run the
start:staging
orbuild:staging
command to start a local staging environment or to build the staging environment bundle
It's important to note that the NODE_ENV
will still be set to development when running the npm start
command and the NODE_ENV
will be set to production when running the npm run build
command, so environment variables can still be loaded from either .env.development
or .env.production
depending on the command used.
For example, running the start:staging
command from above would load environment variables from the following files (in order of priority):
.env.staging
.env.development.local
.env.development
.env.local
.env
Source control
Just like with Parcel, you should commit all your .env
files to source control with the exception of .env*.local
files. The local .env
files should only be used to tweak your app's configurations when running the app locally.
Resources
CRA environment variable documentation
CRA dotenv configuration (repo code)
CRA webpack configuration (repo code)
Gatsby
Gatsby handles environment variables in a unique way compared to Create React App and Parcel. Gatsby uses both dotenv and webpack's DefinePlugin to manage environment variables.
By default, Gatsby supports a development and a production environment, so environment variables should be added to either a .env.development
or a .env.production
file. Gatsby does not support .env
files that do not contain a suffix.
Naming requirements
Any environment variables that are used in the front-end code should be prefixed with GATSBY_
in order for the environment variables to work properly in a deployed environment.
If an environment variable is not used in the front-end code but rather used at some point in the Gatsby build process, it does not need to be prefixed by GATSBY_
when the environment variable is defined in .env
files.
See the "Source control" section below for some exceptions to these naming requirements.
Specifying environments
When running the gatsby develop
command, that will set the NODE_ENV
to development and environment variables will be taken from .env.development
. When building the app using gatsby build
, the NODE_ENV
will be set to production and environment variables will be taken from .env.production
.
If you'd like to have more than a development and a production environment, you can do so by specifying a value for GATSBY_ACTIVE_ENV
when running one of the gatsby commands. For example, if you were to have a .env.staging
file, you would be able to access the variables from that file by running one of the following commands:
1GATSBY_ACTIVE_ENV=staging gatsby develop2GATSBY_ACTIVE_ENV=staging gatsby build
Note: if using GATSBY_ACTIVE_ENV
to select a different .env
file, then the environment variables from .env.development
and .env.production
will be ignored.
Source control
Gatsby recommends that you don't commit your .env
files to source control, but rather add environment variables to your app's hosting provider's deployment tool. This is because sometimes API keys are required to perform certain operations during the Gatsby build step (which is done in a backend-end environment). Because you don't want these private API keys exposed to the public through source control, these API keys should be stored in .env
files that are not committed to source control.
If you do decide to commit your .env
files to source control and make those available on your hosted environment, you do not need to prefix any of your environment variables by GATSBY_
, even if those environment variables are used on the front-end.
Resources
Gatsby environment variable documentation
Gatsby dotenv config (repo code)
Gatsby webpack config (repo code)
A note on environment variables and source control
Almost all the articles I've read on environment variables say to never commit your .env
files to source control, so I've been terrified to do so for so long. However, this is bad advice. If all your environment variables will be embedded in your front-end JavaScript code, then there is no harm in committing your .env
files to source control. Any environment variable used in your front-end code will be viewable in the browser if a user decides to inspect the browser's source code.
In fact, I would recommend committing these files in source control as this will make the installation process for a project much simpler for all developers. It's even OK to commit API keys saved in your environment variables as long as you're sure those API keys are being used in your front-end code and that is how they are intended to be used. For example, if you'd like to embed Google Maps on a web page, it's required to use a Google Maps API key to do that. That API key has no choice but to be exposed to the public since it will be part of an iframe
's src
value.
You might think that having an API key exposed to the public is bad since anyone will be able to use that API key and then potentially max our your API call limits. However, API keys that need to be exposed to the front-end (like the Google Maps API key mentioned above), have ways to restrict the usage of the API keys by restricting usage to certain IP addresses, referrer URLs, and mobile apps. You can read Google's recommendation for API Key Best Practices for more details.
With all that being said, if you are using API keys for back-end code (like what could be the case when building a Gatsby site), then the API keys should be kept in .env
files and those files should not be committed to source control.
Hopefully, this article gives you a better idea of when, why, and how to use environment variables in your front-end JavaScript applications. Let me know if you have other useful tips related to environment variables.
Interested in learning how environment variables should be configured on the back-end with Node.js? Read this excellent article by John Papa.