According to the React docs you can have development
, test
and production
envs.
The value of NODE_ENV
is set automatically to development (when using npm start
), test (when using npm test
) or production (when using npm build
). Thus, from the point of view of create-react-app, there are only three environments.
I need to change root rest api urls based on how I am deployed. e.g.
baseURL = 'http://localhost:3004';
baseURL = 'http://localhost:8080';
baseURL = 'http://uat.api.azure.com:8080';
baseURL = 'http://my.cool.api.com';
How do I configure a UAT environment for react if it only caters for dev, test and prod?
What would my javascript, package.json and build commands look like to switch these values automatically?
Like John Ruddell wrote in the comments, we should still use NODE_ENV=production
in a staging environment to keep it as close as prod as possible. But that doesn't help with our problem here.
The reason why NODE_ENV
can't be used reliably is that most Node modules use NODE_ENV
to adjust and optimize with sane defaults, like Express, React, Next, etc. Next even completely changes its features depending on the commonly used values development
, test
and production
.
So the solution is to create our own variable, and how to do that depends on the project we're working on.
The documentation says:
Note: You must create custom environment variables beginning with
REACT_APP_
. Any other variables exceptNODE_ENV
will be ignored to avoid accidentally exposing a private key on the machine that could have the same name.
It was discussed in an issue where Ian Schmitz says:
Instead you can create your own variable like
REACT_APP_SERVER_URL
which can have default values in dev and prod through the.env
file if you'd like, then simply set that environment variable when building your app for staging likeREACT_APP_SERVER_URL=... npm run build
.
A common package that I use is cross-env
so that anyone can run our npm scripts on any platform.
"scripts": {
"build:uat": "cross-env REACT_APP_SERVER_URL='http://uat.api.azure.com:8080' npm run build"
If we're not bound to CRA, or have ejected, we can easily configure any number of environment configurations we'd like in a similar fashion.
Personally, I like dotenv-extended
which offers validation for required variables and default values.
Similarly, in the package.json
file:
"scripts": {
"build:uat": "cross-env APP_ENV=UAT npm run build"
Then, in an entry point node script (one of the first script loaded, e.g. required in a babel config):
const dotEnv = require('dotenv-extended');
// Import environment values from a .env.* file
const envFile = dotEnv.load({
path: `.env.${process.env.APP_ENV || 'local'}`,
defaults: 'build/env/.env.defaults',
schema: 'build/env/.env.schema',
errorOnMissing: true,
silent: false,
});
Then, as an example, a babel configuration file could use these like this:
const env = require('./build/env');
module.exports = {
plugins: [
['transform-define', env],
],
};
John Ruddell also mentioned that one can detect at runtime the domain the app is running off of.
function getApiUrl() {
const { href } = window.location;
// UAT
if (href.indexOf('https://my-uat-env.example.com') !== -1) {
return 'http://uat.api.azure.com:8080';
}
// PROD
if (href.indexOf('https://example.com') !== -1) {
return 'http://my.cool.api.com';
}
// Defaults to local
return 'http://localhost:3004';
}
This is quick and simple, works without changing the build/CI/CD pipeline at all. Though it has some downsides:
babel-plugin-transform-define
or Webpack's DefinePlugin
resulting in a slightly bigger file size.