Next.js Environment Variables
Manage environment variables in Next.js with env files, the NEXT_PUBLIC prefix, and runtime access.
TL;DR
- Store secrets in
.env.localand read them withprocess.env. - Prefix a name with
NEXT_PUBLIC_to expose it to the browser. - Restart the dev server after you change any
.envfile.
Env Files
.env.localKeep local secrets here; this file is ignored by git by default.
# .env.local (never commit)
DATABASE_URL="postgres://localhost:5432/app"
API_KEY="sk_test_123".envStore shared, non-secret defaults that are safe to commit to the repo.
# .env (safe to commit)
NEXT_PUBLIC_SITE_NAME="Acme"
LOG_LEVEL="info"Load OrderEarlier sources win, so local files override the shared defaults.
1. process.env # real environment
2. .env.development.local
3. .env.local
4. .env.development
5. .env$VARIABLEReuse one variable inside another by referencing it with a dollar sign.
HOST="example.com"
API_URL="https://$HOST/api"Server Variables
process.envRead any variable on the server, where secrets stay private.
const key = process.env.API_KEY;
export async function getWeather() {
return fetch(`https://api.example.com?key=${key}`);
}Server OnlyVariables without the public prefix are undefined in browser code.
'use client';
// undefined in the browser
console.log(process.env.API_KEY);Pass as a PropRead a value on the server and pass it down to a Client Component.
export default function Page() {
const region = process.env.REGION ?? 'us';
return <RegionBadge region={region} />;
}Public Variables
NEXT_PUBLIC_Add this prefix to make a value available in browser JavaScript.
# .env
NEXT_PUBLIC_ANALYTICS_ID="G-12345"Read in the BrowserAccess a public variable from a Client Component just like on the server.
'use client';
export function SiteName() {
const name = process.env.NEXT_PUBLIC_SITE_NAME;
return <span>{name}</span>;
}Static LookupWrite the full variable name, because dynamic lookups are not replaced at build.
// Works: replaced at build time
process.env.NEXT_PUBLIC_API_URL;
// Fails in the browser
const name = 'NEXT_PUBLIC_API_URL';
process.env[name];Validation and Runtime
z.object()Validate required variables once, so a missing value fails with a clear error.
// lib/env.ts
import { z } from 'zod';
export const env = z
.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
})
.parse(process.env);connection()Read a variable at request time instead of when the page is prebuilt.
import { connection } from 'next/server';
export default async function Page() {
await connection();
const flag = process.env.FEATURE_FLAG;
return <p>{flag}</p>;
}loadEnvConfig()Load the same env files in scripts and tools that run outside Next.js.
import { loadEnvConfig } from '@next/env';
loadEnvConfig(process.cwd());Tips
- Validate required variables once at startup with a schema, so a missing
DATABASE_URLfails fast with a clear message. - Commit a
.env.examplefile that lists every variable name without values, so teammates know what to set.
Warnings
- Any variable starting with
NEXT_PUBLIC_is bundled into browser JavaScript, so never put a secret behind that prefix. - Values with the
NEXT_PUBLIC_prefix are inlined when you runnext build, so changing them later requires a new build.
In Practice
Check every required variable at startup and import typed values from one place.
- The schema lists each variable the server needs and the rule it must pass.
- parse throws at startup, so a missing value never reaches production quietly.
- server-only stops this module from being imported into browser code.
- Other files import env, which gives them typed and guaranteed values.
// lib/env.ts
import 'server-only';
import { z } from 'zod';
const schema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
LOG_LEVEL: z.enum(['debug', 'info']).default('info'),
});
export const env = schema.parse(process.env);
// lib/db.ts
import postgres from 'postgres';
import { env } from './env';
export const sql = postgres(env.DATABASE_URL);FAQ
Add them to .env.local as NAME=value lines. Read them on the server with process.env.NAME. Next.js loads the file automatically.
Only variables that start with NEXT_PUBLIC_ reach the browser. Rename the variable, or read it in a Server Component and pass the value down as a prop.
.env holds shared defaults and can be committed. .env.local holds your own secrets and overrides, and it should stay out of git. .env.local wins when both define the same name.
Set them in your hosting provider's dashboard or your container's environment. Do not upload .env.local. Remember that NEXT_PUBLIC_ values must be present at build time.