Fix: process.env.VARIABLE_NAME Is Undefined (Node.js, React, Next.js, Vite)
The Error
You try to access an environment variable and get undefined:
console.log(process.env.DATABASE_URL);
// undefinedOr your app crashes with a TypeError because you tried to use the value:
TypeError: Cannot read properties of undefined (reading 'split')In Next.js (browser-side code):
console.log(process.env.API_URL);
// undefined -- even though it's in your .env fileconsole.log(import.meta.env.API_URL);
// undefinedIn React (Create React App):
console.log(process.env.API_URL);
// undefinedAll of these mean the same thing: the environment variable you’re trying to read doesn’t exist in the current runtime context. The reason depends on your setup.
Why This Happens
Environment variables aren’t automatically available in your code just because you put them in a .env file. Something has to load them.
The most common causes:
- No
.envfile exists. You cloned a repo that has.envin.gitignorebut never created your own.envfile. - The
.envfile is in the wrong directory. Most tools expect it in the project root (next topackage.json). A.envfile insidesrc/or another subdirectory won’t be found. dotenvisn’t installed or configured. Plain Node.js and Express don’t read.envfiles by default. You need thedotenvpackage.- Missing framework-specific prefix. Next.js requires
NEXT_PUBLIC_, Vite requiresVITE_, and Create React App requiresREACT_APP_for variables exposed to the browser. - The dev server wasn’t restarted. Environment variables are loaded at startup. Changing
.envwhile the server is running has no effect until you restart. - In production/Docker, the variables were never set. A
.envfile on your local machine doesn’t magically appear in a Docker container or hosting platform.
Fix 1: Create the .env File with Correct Variable Names
If you don’t have a .env file, create one in your project root (the same directory as package.json):
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=sk-abc123
PORT=3000Rules for .env files:
- No spaces around
=.API_KEY=valueis correct.API_KEY = valuewill include the spaces in the variable name or value depending on the parser. - No quotes needed for simple values.
API_KEY=sk-abc123works fine. If you must use quotes (for values with spaces), use double quotes:MESSAGE="hello world". - One variable per line. No semicolons, no commas.
- No
exportkeyword. WriteAPI_KEY=value, notexport API_KEY=value(unless you’re sourcing the file in a shell script, which is a different use case).
If you cloned a repo, look for a .env.example or .env.sample file. Copy it and fill in your values:
cp .env.example .envFix 2: Use the Right Prefix for Your Framework
Frontend frameworks intentionally limit which environment variables are exposed to the browser. This prevents accidentally leaking secrets like database passwords into client-side JavaScript.
Next.js: NEXT_PUBLIC_ prefix
Only variables starting with NEXT_PUBLIC_ are available in browser-side code:
# .env
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://localhost:5432/mydb// Browser code (components, pages)
console.log(process.env.NEXT_PUBLIC_API_URL); // "https://api.example.com"
console.log(process.env.DATABASE_URL); // undefined (intentionally hidden)Variables without the prefix are still available in server-side code (API routes, getServerSideProps, Server Components, Route Handlers):
// app/api/users/route.ts (server-side)
console.log(process.env.DATABASE_URL); // works hereVite: VITE_ prefix
Vite uses import.meta.env instead of process.env. Only variables starting with VITE_ are exposed:
# .env
VITE_API_URL=https://api.example.com
SECRET_KEY=abc123console.log(import.meta.env.VITE_API_URL); // "https://api.example.com"
console.log(import.meta.env.SECRET_KEY); // undefinedNote: process.env does not exist in Vite projects by default. If you see process is not defined, switch to import.meta.env.
Create React App: REACT_APP_ prefix
CRA exposes variables starting with REACT_APP_:
# .env
REACT_APP_API_URL=https://api.example.com
SECRET_KEY=abc123console.log(process.env.REACT_APP_API_URL); // "https://api.example.com"
console.log(process.env.SECRET_KEY); // undefinedNote: Create React App is no longer actively maintained. If you’re starting a new project, consider Next.js, Vite, or Remix instead.
Fix 3: Install and Configure dotenv for Node.js / Express
Plain Node.js does not read .env files. You need the dotenv package.
Install it:
npm install dotenvThen load it at the very top of your entry file, before any other imports:
require('dotenv').config();
// Now process.env.DATABASE_URL is available
const express = require('express');If you’re using ES modules (import syntax):
import 'dotenv/config';
import express from 'express';The import order matters. If you import a module that reads process.env before dotenv runs, the variable will be undefined in that module. Always import dotenv first.
Node.js 20.6+ built-in .env support
Node.js 20.6 and later can load .env files without dotenv:
node --env-file=.env app.jsThis loads the .env file before your code runs. No package needed. You can also specify multiple files:
node --env-file=.env --env-file=.env.local app.jsCustom .env path
If your .env file isn’t in the project root, pass the path to dotenv:
require('dotenv').config({ path: './config/.env' });Or with the --env-file flag:
node --env-file=./config/.env app.jsFix 4: Restart the Dev Server
Environment variables are read when your application starts. If you add or change a variable in .env while the dev server is running, the change won’t take effect until you restart.
Stop the server (Ctrl+C) and start it again:
npm run devThis applies to every framework: Next.js, Vite, CRA, Express, and anything else that reads .env at startup.
Note: Some tools like Vite do support hot-reloading .env changes in newer versions, but the safest approach is always to restart.
Fix 5: Set Environment Variables in Docker and Production
A .env file is a local development convenience. In production, environment variables must be set through your deployment platform.
Docker
Option 1: --env-file flag:
docker run --env-file .env my-appOption 2: -e flag for individual variables:
docker run -e DATABASE_URL=postgresql://db:5432/mydb my-appOption 3: env_file in Docker Compose:
# docker-compose.yml
services:
app:
build: .
env_file:
- .envOption 4: ENV instruction in Dockerfile (for non-sensitive defaults):
ENV NODE_ENV=production
ENV PORT=3000Warning: Never put secrets (API keys, database passwords) in a Dockerfile. The values are baked into the image and visible to anyone who can pull it. Also watch out for Docker permission issues when running containers.
Hosting platforms
Set environment variables through your hosting platform’s UI or CLI:
Vercel:
vercel env add DATABASE_URLOr set it in the Vercel dashboard under Settings > Environment Variables.
Railway, Render, Fly.io: Each has an environment variables section in the dashboard.
AWS (ECS, Lambda, Elastic Beanstalk): Use the respective service’s environment configuration, AWS Systems Manager Parameter Store, or AWS Secrets Manager.
The common mistake is deploying an app that relies on a .env file when the file isn’t included in the Docker image or deployment artifact (because it’s in .gitignore).
Edge Cases
.env.local vs .env load order
Next.js loads environment files in this order (later files override earlier ones):
.env(base defaults).env.local(local overrides, not committed to git).env.development/.env.production/.env.test(environment-specific).env.development.local/.env.production.local/.env.test.local(environment-specific, local overrides)
.env.local is not loaded in the test environment to ensure test consistency.
Vite follows a similar pattern:
.env.env.local.env.[mode](e.g.,.env.development).env.[mode].local
If a variable is defined in multiple files, the most specific file wins. If DATABASE_URL is in both .env and .env.local, the .env.local value is used.
.env is gitignored but no .env.example exists
If .env is in .gitignore (as it should be), other developers who clone the repo won’t have it. Create a .env.example file that lists all required variables without their actual values:
# .env.example
DATABASE_URL=
API_KEY=
NEXT_PUBLIC_API_URL=Commit this file to the repository. Add a note in your README telling developers to copy it:
cp .env.example .envVariables available on the server but not the client
In Next.js and similar frameworks, this is intentional. If you need a variable on the client, rename it with the framework’s prefix (NEXT_PUBLIC_, VITE_, etc.).
If you need a secret value on the client side (rare, and usually a design issue), fetch it from an API route instead of exposing it as an environment variable.
dotenv doesn’t override existing environment variables
By default, dotenv does not overwrite variables that already exist in the environment. If DATABASE_URL is already set in your shell, the .env file value is ignored.
To force overwriting:
require('dotenv').config({ override: true });This matters when you have conflicting values between your shell environment and your .env file.
Still Not Working?
Typos in variable names
Environment variable names are case-sensitive. process.env.Database_Url is not the same as process.env.DATABASE_URL. Double-check the exact name in your .env file matches what you’re reading in code.
Spaces around =
This is wrong:
API_KEY = sk-abc123Depending on the parser, this sets a variable named API_KEY (with a trailing space) to sk-abc123 (with a leading space), or it fails silently. Write it without spaces:
API_KEY=sk-abc123Quotes around values
Most .env parsers strip surrounding quotes:
# Both produce the string: hello world
MESSAGE="hello world"
MESSAGE='hello world'But some parsers (especially shell-based ones) behave differently. If you’re getting unexpected quote characters in your values, try removing the quotes. Only use them for values that contain spaces or special characters.
Multiline values
If your variable contains a newline (like an RSA private key), wrap it in double quotes and use \n:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----"Or use the actual newline syntax supported by dotenv:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIE...
-----END RSA PRIVATE KEY-----".env file encoding
Your .env file should be UTF-8 without BOM. Some Windows editors add a BOM (byte order mark) to the beginning of the file, which can cause the first variable to be unreadable. If your first environment variable is always undefined but the rest work, re-save the file as UTF-8 without BOM.
Comments in .env files
Lines starting with # are treated as comments:
# This is a comment
API_KEY=sk-abc123But inline comments are tricky. Some parsers support them, others don’t:
API_KEY=sk-abc123 # this might breakTo be safe, put comments on their own line.
process.env values are always strings
Environment variables are always strings. If you expect a number or boolean, you need to convert:
// Wrong -- this is the string "3000", not the number 3000
const port = process.env.PORT;
// Correct
const port = parseInt(process.env.PORT, 10) || 3000;
// Wrong -- this is the string "true", not the boolean true
if (process.env.DEBUG) { /* always true if set to anything, even "false" */ }
// Correct
const debug = process.env.DEBUG === 'true';Related: If you’re getting module import errors in your Node.js app, see Fix: Error Cannot find module.
Related Articles
Fix: Next.js Image Optimization Errors – Invalid src, Missing Loader, or Unoptimized
How to fix Next.js Image component errors including 'Invalid src prop', 'hostname not configured', missing loader, and optimization failures in production.
Fix: React useEffect runs infinitely (infinite loop / maximum update depth exceeded)
How to fix useEffect infinite loops in React — covers missing dependency arrays, referential equality, useCallback, unconditional setState, data fetching cleanup, event listeners, useRef, previous value comparison, and the exhaustive-deps lint rule.
Fix: Hydration failed because the initial UI does not match what was rendered on the server (Next.js)
How to fix the Next.js hydration mismatch error. Covers invalid HTML nesting, browser extensions, Date/time differences, useEffect for client-only code, dynamic imports, suppressHydrationWarning, localStorage, third-party scripts, Math.random, auth state, and React portals.
Fix: Loading chunk failed / ChunkLoadError
How to fix 'Loading chunk failed', 'ChunkLoadError', and 'Failed to fetch dynamically imported module' in webpack, Next.js, React, and Vite. Covers stale deployments, CDN caching, publicPath misconfiguration, service worker cache, code splitting, dynamic import retry strategies, React.lazy error boundaries, and Next.js-specific solutions.