NODE_ENV
Specifies the environment in which the Node.js application is running. Common values are development, production, and test. Many frameworks and libraries use this to toggle behavior such as verbose logging, minification, and caching strategies.
Last updated:
NODE_ENV tells your application which environment it is running in — almost always 'production' or 'development'. The catch most developers miss: Node.js core does not read NODE_ENV at all. It is a userland convention. Express uses it to cache compiled views and skip stack traces; React and bundlers strip development warnings and dead code when it is 'production'; npm skips devDependencies on `npm install` when it is set to 'production' — still current behavior in npm 10 and 11, where it sets the default for `--omit=dev`. Setting it has no effect unless something downstream checks it.
- Provider
- Node.js
- Category
- runtime
- Set by
- Set manually in the shell, process manager, or deployment configuration
- Example
- production
How to set NODE_ENV
bash
export NODE_ENV=production
node server.jsinline (one command)
NODE_ENV=production node server.jsDocker
ENV NODE_ENV=productiondocker-compose
services:
app:
environment:
NODE_ENV: productionWho actually reads NODE_ENV?
Not Node.js. The runtime starts identically whether NODE_ENV is production, development, or unset — no flag flips, no optimization kicks in. The variable only matters because the ecosystem standardized on checking it:
- Express reads it into
app.get('env'). In production mode it caches compiled view templates and stops sending stack traces to the client. The Express performance docs list setting NODE_ENV=production as worth roughly a 3x throughput improvement on template-heavy apps. - npm defaults its
omitconfig todevwhen NODE_ENV=production — sonpm installon a production box silently skips devDependencies. This is current behavior, not legacy: the npm config docs still document it for npm 10/11. - Bundlers (webpack, Vite, esbuild) replace
process.env.NODE_ENVwith a string literal at build time, which is how React's development warnings and entire dev-only code paths get dead-code-eliminated out of production bundles. Ship a React app bundled with NODE_ENV=development and you pay for it on every render. - Test runners — Jest and Vitest both set NODE_ENV=test when it is not already set, which is why config files can branch on it without you ever exporting it.
The corollary: if nothing in your dependency tree checks process.env.NODE_ENV, setting it is a no-op. It is a contract between libraries, not a runtime switch.
When is NODE_ENV read — build time or runtime?
Both, and confusing the two is the most common NODE_ENV bug. Server-side code reads it at runtime from the actual process environment. Browser code can't — there is no process environment in a browser — so the bundler bakes the value in when you build:
// Server: evaluated at runtime, every time
if (process.env.NODE_ENV === 'production') enableCache();
// Browser bundle: the bundler rewrote this at BUILD time to
if ('production' === 'production') enableCache();
// ...and then dead-code-eliminated the branch entirely.That means rebuilding is the only way to change NODE_ENV-gated behavior in frontend code. Setting the variable on your web server after the fact does nothing — the decision was made when the bundle was written. Next.js goes one step further and overrides NODE_ENV entirely based on the command: next dev forces development, next build and next start force production, and it prints a "non-standard NODE_ENV" warning if you fight it. Vite similarly sets NODE_ENV=production during vite build even when you pass --mode staging — mode and NODE_ENV are deliberately separate concepts there.
Why is NODE_ENV=staging a bad idea?
Because nearly every library that checks NODE_ENV uses the test === 'production', anything else — staging, qa, preprod — falls into the development bucket. Your staging environment then runs with template caching off, verbose errors on, development React, and devDependencies installed: the one environment meant to mirror production mirrors your laptop instead. Keep NODE_ENV binary (production everywhere that serves traffic, development locally) and put the deployment-stage concept in a separate variable like APP_ENV or DEPLOY_ENV. The environment variable best practices guide covers this split in more depth.
The npm install trap, step by step
This one produces genuinely confusing CI failures. A Dockerfile that sets the environment before installing:
ENV NODE_ENV=production
COPY package*.json ./
RUN npm install # devDependencies silently skipped
COPY . .
RUN npm run build # fails: tsc / vite / webpack not installedThe build tool lives in devDependencies, npm skipped it because NODE_ENV said production, and the error message ("vite: not found") points nowhere near the cause. Either set NODE_ENV after the build stage, or be explicit: npm install --include=dev for the build, npm ci --omit=dev for the runtime image. Multi-stage Docker builds make this clean — the Docker environment variables guide shows the pattern.
How should I set it per environment?
Locally, most setups leave NODE_ENV unset or put it in a .env file — though note that Next.js and Vite ignore NODE_ENV from .env files precisely because they manage it themselves. In production, set it in the process manager or platform config, not in code: Heroku, Render, and Railway set NODE_ENV=production for Node apps by default; on bare servers it belongs in the systemd unit or PM2 ecosystem file. For the full runtime picture — how process.env works, ordering, typing — see the Node.js environment variables guide.
When NODE_ENV is the wrong tool
Don't hang feature flags, region switches, or customer-specific behavior off NODE_ENV — it changes too much at once (logging, caching, error detail, dependency installs) to be a precision instrument. And never gate security behavior on it: "auth disabled unless production" is one typo or one unset variable away from auth disabled in production. NODE_ENV answers exactly one question — "is this an optimized deployment or a developer workstation?" — and it should keep answering only that.
References
Frequently Asked Questions
Does Node.js itself change behavior based on NODE_ENV?
No. Node.js core ignores NODE_ENV entirely. Only libraries and frameworks (Express, React, webpack, npm) read it. If nothing in your stack checks process.env.NODE_ENV, setting it does nothing.
What values are valid for NODE_ENV?
There is no enforced set — it is just a string. The de-facto values are 'production', 'development', and 'test'. Avoid inventing custom values like 'staging' for NODE_ENV; most libraries treat anything that is not 'production' as development. Use a separate variable (e.g. APP_ENV) for finer-grained environments.
Stay up to date
Get notified about new guides, tools, and cheatsheets.