Environment Variables

Environment Variables and Secrets

Next.js 10,514 loads .env* files into process.env with no dotenv 20,543 dependency of your own. Five sources are consulted in order and the first hit wins: process.env, .env.$(NODE_ENV).local, .env.local, .env.$(NODE_ENV), .env. NODE_ENV is development under next dev and production otherwise; the only other value allowed is test, and in it .env.local is skipped so everyone's tests agree. Keep the files in the project root even when your code lives in src/; $NAME expands to another variable:

.env in the project root — never in gitTSX
NEXT_PUBLIC_SITE_NAME=Widget Shop
API_BASE=https://api.example.com
DB_PASSWORD=hunter2-from-dotenv
SUPPORT_URL=$API_BASE/support

The prefix is the whole security model. A NEXT_PUBLIC_* variable is inlined at build time: every textual occurrence of it in code that reaches the browser is replaced by the literal string. Anything else stays on the server.

The NEXT_PUBLIC_ prefix decides which side of the network a value lands on
The NEXT_PUBLIC_ prefix decides which side of the network a value lands on

Test it once so you trust it. Put the site name in a Client Component, run next build, then grep what shipped: grep -rl "Widget Shop" .next/static matched a chunk, while grep -rl "hunter2-from-dotenv" .next/static matched nothing. The replacement is literal:

Output of 84
0,function(){return(0,t.jsxs)("p",{children:["Welcome to ","Widget Shop"]})}])}]);

A Server Component printing SUPPORT_URL rendered https://api.example.com/support, so $API_BASE expanded as documented. Because the substitution is textual, process.env[varName] and a destructured process.env are not inlined; they arrive as undefined.

Inlining also freezes the value into the artifact, so one Docker 514 image promoted from staging to production carries staging's values forever. Read anything that must vary per environment on the server during dynamic rendering — await connection() from next/server opts the component out of static rendering, so process.env.MY_VALUE is read per request — and pass it down as a prop.