Skip to content

Environment Variables

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.

Key Takeaways

  • Next.js automatically loads .env/.env.local/.env.development/.env.production files.
  • 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.