0
votes

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.

  • development: baseURL = 'http://localhost:3004';
  • test: baseURL = 'http://localhost:8080';
  • uat: baseURL = 'http://uat.api.azure.com:8080';
  • production: 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?

1
Use your own APP_ENV variable.Emile Bergeron
I also use dotenv-extended which works really well with different .env files, say .env.uat.Emile Bergeron
UAT is essentially staging no? UAT should be as similar as possible to production, where it exposes new features in the pipeline to be released to prod.John Ruddell
I agree dev and prod are different and thats where define plugin is good. I just am saying uat and prod are and should be the same. Uploaded to different servers is CI/CD deployment when you make a pull request + merge code into a branch. separate from the JS codeJohn Ruddell
UAT and PROD are similar, but not identical, since they will be deployed to different servers and have different URLs.opticyclic

1 Answers

1
votes

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.

Additional environments with Create React App (CRA)

The documentation says:

Note: You must create custom environment variables beginning with REACT_APP_. Any other variables except NODE_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 like REACT_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"

Any other JS project

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],
  ],
};

Runtime configuration

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:

  • All the configuration is "leaked" in the final build,
  • It won't benefit from dead-code removal at minification time when using something like babel-plugin-transform-define or Webpack's DefinePlugin resulting in a slightly bigger file size.
  • Won't be available at compile time.
  • Trickier if using Server-Side Rendering (though not impossible)