Environment variables let you configure secrets and settings differently across development, preview, and production without hardcoding them into your source code. This lesson covers Next.js's .env conventions and the crucial NEXT_PUBLIC_ prefix.
.env Files and Loading Order
Next.js automatically loads environment variables from .env, .env.local, .env.development, and .env.production files (with more specific files taking priority), with no extra library required. By convention, .env.local is used for secrets and machine-specific overrides and should never be committed to version control.
By default, environment variables are only available on the server — they never reach client-side JavaScript. This is a deliberate security boundary that prevents accidentally shipping a secret API key to every visitor's browser.
# .env.local (never commit this file)
DATABASE_URL="postgres://user:pass@localhost:5432/mydb"
NEXT_PUBLIC_ANALYTICS_ID="UA-12345"
DATABASE_URL stays server-only; NEXT_PUBLIC_ANALYTICS_ID is intentionally exposed to the browser, because of its prefix.
Server-Only vs. Client-Exposed Variables
// Server-only (Server Components, Route Handlers, Server Actions)
process.env.DATABASE_URL
// Exposed to the client too (must use this exact prefix)
process.env.NEXT_PUBLIC_ANALYTICS_ID
Variables without the NEXT_PUBLIC_ prefix are only accessible in server-side code.
Variables with the NEXT_PUBLIC_ prefix are inlined into the client bundle at build time.
Never put secrets (API keys, database URLs) behind the NEXT_PUBLIC_ prefix.
.env.local should always be listed in .gitignore to avoid committing secrets.
Environment Variables Cheat Sheet
File conventions and their purposes.
File
Purpose
.env
Defaults, safe to commit (no secrets)
.env.local
Local secrets and overrides, never committed
.env.development
Development-specific defaults
.env.production
Production-specific defaults
NEXT_PUBLIC_*
Explicitly exposed to client-side JavaScript
Why NEXT_PUBLIC_ Is a Deliberate Boundary
Requiring an explicit prefix for client-exposed variables — rather than exposing everything by default, or nothing by default with a manual opt-in per use — makes the security boundary visible directly in the variable's name, wherever it's used in code, reducing the chance of an accidental leak going unnoticed in review.
Validating Environment Variables at Startup
For anything beyond a small project, it's worth validating that required environment variables are actually present and correctly typed at startup, rather than discovering a missing variable only when a specific code path runs in production.
// lib/env.ts
const requiredEnvVars = ["DATABASE_URL", "AUTH_SECRET"] as const;
for (const key of requiredEnvVars) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
Common Mistakes
Prefixing a secret with NEXT_PUBLIC_ by mistake, shipping it to every visitor's browser.
Committing .env.local to version control instead of adding it to .gitignore.
Assuming environment variables update without restarting the dev server after editing .env.local.
Not validating that required environment variables are present, only discovering it at runtime in production.
Variables are server-only by default, which is an intentional security boundary.
Only variables prefixed NEXT_PUBLIC_ are exposed to client-side JavaScript.
.env.local should never be committed to version control.
Validate required environment variables at startup to catch missing configuration early.
Pro Tip
Keep a .env.example file (with placeholder values, safely committed to version control) documenting every environment variable your project expects — this makes onboarding new developers (and remembering your own setup months later) significantly easier than reconstructing the list from scattered process.env references in code.
You now understand environment variables. Next, learn what actually happens during a Next.js production build.