Two kinds of variable
| Prefix | Visibility |
|---|---|
| (no prefix) | Server-only. Read at runtime. Never reaches the browser. |
NEXT_PUBLIC_ | Inlined into the client bundle at build time. Visible in DevTools. |
The build-time gotcha
NEXT_PUBLIC_* values are baked in when you run next build. Re-deploying the same Docker image to staging and prod gives you the same values; you can't change them by setting an env var on the running container.
Loading order
process.env(already-set values win).env.production.local/.env.development.local.env.local(skipped in test).env.production/.env.development.env
Classify environment variables by exposure time and audience. Keep secrets unprefixed and server-only; treat every NEXT_PUBLIC_ value as public and frozen into the client bundle at build time. Validate required server variables during startup with a schema. Public and secret variables with similar names are easy to miss in review. Automate client-bundle scans and deployment checks, and record whether each secret rotation requires a rebuild or only a restart.
A value stored in a deployment dashboard is not automatically secret. Once referenced through a public prefix, it can appear in shipped JavaScript. Likewise, promoting one prebuilt image across environments cannot change public values that were already compiled. Build with a recognizable public marker, change the runtime environment, and inspect the client bundle and page behavior. Remove a required secret and confirm startup fails clearly. Search build artifacts and browser traffic for values that must remain private.