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);
// undefined

Or 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 file

In Vite:

console.log(import.meta.env.API_URL);
// undefined

In React (Create React App):

console.log(process.env.API_URL);
// undefined

All 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 .env file exists. You cloned a repo that has .env in .gitignore but never created your own .env file.
  • The .env file is in the wrong directory. Most tools expect it in the project root (next to package.json). A .env file inside src/ or another subdirectory won’t be found.
  • dotenv isn’t installed or configured. Plain Node.js and Express don’t read .env files by default. You need the dotenv package.
  • Missing framework-specific prefix. Next.js requires NEXT_PUBLIC_, Vite requires VITE_, and Create React App requires REACT_APP_ for variables exposed to the browser.
  • The dev server wasn’t restarted. Environment variables are loaded at startup. Changing .env while the server is running has no effect until you restart.
  • In production/Docker, the variables were never set. A .env file 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=3000

Rules for .env files:

  • No spaces around =. API_KEY=value is correct. API_KEY = value will include the spaces in the variable name or value depending on the parser.
  • No quotes needed for simple values. API_KEY=sk-abc123 works 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 export keyword. Write API_KEY=value, not export 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 .env

Fix 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 here

Vite: 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=abc123
console.log(import.meta.env.VITE_API_URL); // "https://api.example.com"
console.log(import.meta.env.SECRET_KEY);   // undefined

Note: 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=abc123
console.log(process.env.REACT_APP_API_URL); // "https://api.example.com"
console.log(process.env.SECRET_KEY);        // undefined

Note: 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 dotenv

Then 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.js

This 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.js

Custom .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.js

Fix 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 dev

This 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-app

Option 2: -e flag for individual variables:

docker run -e DATABASE_URL=postgresql://db:5432/mydb my-app

Option 3: env_file in Docker Compose:

# docker-compose.yml
services:
  app:
    build: .
    env_file:
      - .env

Option 4: ENV instruction in Dockerfile (for non-sensitive defaults):

ENV NODE_ENV=production
ENV PORT=3000

Warning: 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_URL

Or 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):

  1. .env (base defaults)
  2. .env.local (local overrides, not committed to git)
  3. .env.development / .env.production / .env.test (environment-specific)
  4. .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:

  1. .env
  2. .env.local
  3. .env.[mode] (e.g., .env.development)
  4. .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 .env

Variables 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-abc123

Depending 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-abc123

Quotes 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-abc123

But inline comments are tricky. Some parsers support them, others don’t:

API_KEY=sk-abc123 # this might break

To 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