Environment Variables
.env files and exposing a controlled subset of them to the browser when you explicitly ask for it..env.local for secrets
.env.local at the project root. Next.js loads it automatically and, critically, it is gitignored by default in every Next.js starter — so secrets never get committed to version control..env.local
DATABASE_URL=postgres://user:password@localhost:5432/mydb STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxx NEXT_PUBLIC_ANALYTICS_ID=G-ABC123XYZ
Reading a variable on the server
// This code only ever runs on the server (Server Component,
// Route Handler, Server Action), so it is safe to read a secret here.
async function getUser(id: string) {
const res = await fetch(`${process.env.DATABASE_URL}/users/${id}`)
return res.json()
}The NEXT_PUBLIC_ prefix
NEXT_PUBLIC_. At build time, Next.js inlines the value directly into the client JavaScript bundle wherever that variable is referenced.A NEXT_PUBLIC_ variable used in a Client Component
'use client'
export default function Analytics() {
// Inlined at build time — visible to anyone who opens dev tools.
const id = process.env.NEXT_PUBLIC_ANALYTICS_ID
return <span data-analytics-id={id} />
}NEXT_PUBLIC_ ends up baked into the JavaScript that ships to every visitor's browser. It is readable by anyone who opens dev tools or views the page source. Never put an API secret, private key, or database credential behind that prefix — use it only for values that are already safe to be public, like an analytics ID or a public API base URL..env file variants and precedence
.env file names, loaded in a specific order so more specific files can override more general ones.File | Loaded when | Committed to git? | Typical contents |
|---|---|---|---|
| Always | Yes | Safe defaults shared by every environment |
| Always (overrides | No (gitignored) | Local secrets, personal overrides |
| Only during | Yes | Dev-specific defaults |
| Only during | Yes | Production-specific defaults |
| Matching env only | No (gitignored) | Environment-specific secrets |
process.env (already set in the shell) → .env.$(NODE_ENV).local → .env.local → .env.$(NODE_ENV) → .env. A value defined in an earlier (higher-precedence) source wins and is never overwritten by a later file.Reading variables in code
Accessing process.env
// Server-only: works because this file never runs in the browser
const dbUrl = process.env.DATABASE_URL
// Available on both server and client, because of the prefix
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID
if (!dbUrl) {
throw new Error('DATABASE_URL is not set')
}Put secrets in
.env.local— it is loaded automatically and gitignored by default.Prefix a variable with
NEXT_PUBLIC_only to intentionally expose it to the browser.Server-only variables (no prefix) are stripped from the client bundle and are safe for API keys.
.env,.env.local,.env.development,.env.production(and their.localvariants) stack with a defined precedence.Restart
next devafter editing an.envfile — values are read once at process start.