Don't be fooled by NODE_ENV

When reviewing pull requests, any use of NODE_ENV like the snippet below is an automatic flag 🚩.

if (process.env.NODE_ENV === 'production') {
  onlyCallThisInProd();
}

The question to ask is:

Is this checking for our production application environment (i.e. example.com) or did you really intend to check if this is the production JavaScript build?

What is NODE_ENV anyway?#

NODE_ENV is used by many frameworks and packages like react-dom to optimize JavaScript build output, among other things.

NODE_ENV is expected to be development for local development environments and production for deployed application environments.

Application environments#

I use the term "Application Environment" or even "Deployed Application Environment" to disambiguate the term "Environment". The _ENV in NODE_ENV is cleary short for "Environment" but you most likely have other environments at play like local, test, staging, production, etc.

Application environments may have different domains or subdomains but they most likely all (except local) have NODE_ENV set to production so they behave as close as possible to your live production environment (the one your real uses are accessing).

namesubdomainNODE_ENV
locallocalhost:3000development*
testtest.example.comproduction
stagingstaging.example.comproduction
productionexample.comproduction

* While most of the time your local environment will be running with NODE_ENV=development, it should be possible to run with NODE_ENV=production as well. This is possible in both Next.js and Create React App by running npm run build.

So what should we do instead?#

Back to this snippet (still flagged! 🚩), how can we improve it?

if (process.env.NODE_ENV === 'production') {
  onlyCallThisInProd();
}

Option 1 (preferred ✅): Use explicit environment variables#

if (process.env.THING_ENABLED === 'true') {
  onlyCallThisInProd();
}
namesubdomainTHING_ENABLED
locallocalhost:3000false
testtest.example.comfalse
stagingstaging.example.comfalse
productionexample.comtrue

I prefer this option because it provides fine-grained control of execution across environments. The one downside is that it results in more environment variables, which can be cumbersome to manage.

Option 2: Use a separate environment variable that corresponds to your application environment#

To be clear, I do not recommend this option. I am only listing it here because inevitably you will consider it to salve the pain of cumbersome environment variables.

if (process.env.APP_ENV === 'production') {
  onlyCallThisInProd();
}
namesubdomainAPP_ENV
locallocalhost:3000local
testtest.example.comtest
stagingstaging.example.comstaging
productionexample.comproduction

⚠️ This option looks tempting but it has a few downsides

  • Introducing new application environments requires code changes in areas where APP_ENV is evaluated.
  • It is difficult to target specific behavior, e.g. if you wanted to partially emulate production in your local environment by allowing onlyCallThisInProd to be called, you don't have the ability to enable just that. There may be undesirable consequence to setting APP_ENV=production locally because it controls too many things.

Thanks for reading. Find me on Twitter if you have alternative suggestions.