Next.js reads .env files from the project root into process.env. Variables are server-only by default. Prefix a name with NEXT_PUBLIC_ and Next.js copies its value into the browser JavaScript during next build, so it is public and frozen until you rebuild. Never put a secret behind that prefix.
| Order | Source | Notes |
|---|---|---|
| 1 | process.env | Values already set by the shell or host always win |
| 2 | .env.$(NODE_ENV).local | For example .env.development.local |
| 3 | .env.local | Skipped when NODE_ENV is test |
| 4 | .env.$(NODE_ENV) | For example .env.production |
| 5 | .env | Shared defaults for every environment |
Updated
Paste the contents of a .env file below. The checker sorts each variable into server only or sent to the browser, and flags public names that look like secrets. It runs entirely on this page; nothing you paste is uploaded.
Paste .env lines
# Database
DB_HOST=localhost
DB_PASS=s3cret
DATABASE_URL=postgres://app:$DB_PASS@$DB_HOST/app
# Public
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_test_1233 server only, 2 sent to the browser
| Variable | Available in | Note |
|---|---|---|
| DB_HOST | Server only | |
| DB_PASS | Server only | |
| DATABASE_URL | Server only | References DB_PASS, DB_HOST, which Next.js expands. |
| NEXT_PUBLIC_SITE_URL | Server and browser (inlined at build) | |
| NEXT_PUBLIC_STRIPE_SECRET_KEY | Server and browser (inlined at build) | Name looks like a secret. Anything with NEXT_PUBLIC_ is visible to every visitor. |
DB_URL has no prefix, so it exists only on the server. Route Handlers and Server Components can read it freely.
// app/api/health/route.ts
export async function GET() {
const hasDb = Boolean(process.env.DB_URL);
return Response.json({ database: hasDb ? "configured" : "missing" });
}The full literal name is required. At build time Next.js swaps it for the value, so the browser sees a plain string.
"use client";
export default function Footer() {
return <a href={process.env.NEXT_PUBLIC_SITE_URL}>Home</a>;
}Awaiting connection() makes the page render dynamically, so the value comes from the running server instead of the build.
import { connection } from "next/server";
export default async function Page() {
await connection();
const region = process.env.APP_REGION ?? "unknown";
return <p>Served from {region}</p>;
}loadEnvConfig applies the same load order, which keeps scripts and test setups in step with the app.
// env-config.ts
import { loadEnvConfig } from "@next/env";
loadEnvConfig(process.cwd());
The prefix is an instruction to the bundler. During next build, every literal process.env.NEXT_PUBLIC_SOMETHING in your code is replaced with the value from the environment where the build ran, and that text ends up in the JavaScript files anyone can download.
That has two consequences. First, a public variable is never secret, no matter how the file is named. Second, it is frozen: promoting one build from staging to production carries the staging value with it.
Server code can read any variable, public or not. To make sure a value is read per request rather than at build time, render the page dynamically, for example by awaiting connection() from next/server before you read process.env.
The browser cannot read server variables at all. If a Client Component needs a runtime value, fetch it from your own Route Handler or pass it in as a prop from a Server Component.
A helper that reads an API key can be imported into a Client Component by accident. Add import 'server-only' at the top of such files and the build fails if a client file pulls them in.
Without the NEXT_PUBLIC_ prefix a variable only exists on the server, and Next.js replaces it with an empty string in client code. Add the prefix only if the value is safe to publish, then build again.
Public values are inlined when next build runs. A deployed bundle keeps the old value until you build again with the new one.
No. When NODE_ENV is test, Next.js skips .env.local so every test run gets the same values. Put test defaults in .env.test and personal overrides in .env.test.local.
Yes. Write $OTHER_NAME inside a value and Next.js expands it. If the value needs a literal dollar sign, escape it as \$.
If you do not set it, next dev uses development and other commands use production. The allowed values are production, development and test.
Related: Next.js App Router, Next.js API routes, Next.js authentication, Next.js vs Node.js, Deploy a React app.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.