Next.js Environment Variables
Manage environment variables and configure Next.js for different environments.
TL;DR
- 01Use .env.local for environment variables in development.
- 02Variables prefixed NEXT_PUBLIC_ are exposed to browser.
- 03Server-only variables are only available on the server.
Tips
- 01Use .env.example to document which environment variables are needed — commit it to version control.
Warnings
- 01Never commit .env.local files — they contain secrets. Use .gitignore to exclude them.
Environment Files
- Create .env.local for local development variables.
# .env.local DATABASE_URL=postgresql://user:password@localhost/db API_SECRET=my-secret-key - Create .env.production.local for production secrets — Next.js loads it only in production and it is gitignored by default.
# .env.production.local DATABASE_URL=postgresql://user:password@prod/db API_SECRET=production-secret - Variables are loaded at build time for static files.
- Use .env.development to set variables for npm run dev only.
# .env.development NEXT_PUBLIC_API_URL=http://localhost:4000 LOG_LEVEL=debug - Use .env for shared defaults across all environments.
# .env (committed — no secrets) NEXT_PUBLIC_APP_NAME=My App NEXT_PUBLIC_SUPPORT_EMAIL=support@example.com - Next.js uses first-definition-wins: files are checked highest to lowest priority.
# Priority (highest to lowest): # 1. .env.{NODE_ENV}.local (e.g. .env.development.local) # 2. .env.local (skipped in test environment) # 3. .env.{NODE_ENV} (e.g. .env.development) # 4. .env (shared defaults) # The FIRST file that defines a variable wins — later files cannot override it.
Public Variables
- Prefix with NEXT_PUBLIC_ to expose to browser.
# .env.local NEXT_PUBLIC_API_URL=https://api.example.com NEXT_PUBLIC_APP_NAME=My App - Access in client and server code.
function Component() { const apiUrl = process.env.NEXT_PUBLIC_API_URL; return <p>API: {apiUrl}</p>; } - Use for non-sensitive data only.
- Public variables are inlined at build time, not at runtime.
// process.env.NEXT_PUBLIC_API_URL becomes a string literal in the bundle const url = process.env.NEXT_PUBLIC_API_URL; // "https://api.example.com" - Use for things like Stripe publishable keys or analytics IDs.
NEXT_PUBLIC_STRIPE_KEY=pk_live_abc123 NEXT_PUBLIC_GA_ID=G-XXXXXXX
Server-Only Variables
- Variables without NEXT_PUBLIC_ are server-only.
# .env.local DATABASE_URL=postgresql://... API_KEY=secret-key - Access only in server-side code.
export async function GET() { const apiKey = process.env.API_KEY; // Safe: runs on server only } - Never available in browser or client components.
- Use server-only variables in server actions and route handlers.
"use server"; export async function deleteUser(id: string) { await db.user.delete({ where: { id } }); // process.env.DATABASE_URL is safe here } - Attempting to access server-only vars in client code returns undefined.
"use client"; // process.env.API_KEY is undefined — not leaked to browser
Runtime Config and Validation
- Expose build-time variables to the client via the env key in next.config.js.
// next.config.js module.exports = { env: { APP_VERSION: process.env.npm_package_version, // inlined at build time FEATURE_FLAG: process.env.FEATURE_FLAG } }; - Use serverRuntimeConfig for values available only on the server at runtime.
// next.config.js module.exports = { serverRuntimeConfig: { mySecret: process.env.MY_SECRET // server-only, not in browser }, publicRuntimeConfig: { apiUrl: process.env.NEXT_PUBLIC_API_URL // shared with client } }; - Validate required environment variables at startup to catch missing config immediately.
// lib/env.ts — imported in app/layout.tsx const required = ["DATABASE_URL", "AUTH_SECRET"]; for (const key of required) { if (!process.env[key]) throw new Error(`Missing env var: ${key}`); } - Use the t3-env or envalid library for type-safe, schema-validated environment variables.
import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; export const env = createEnv({ server: { DATABASE_URL: z.string().url() }, client: { NEXT_PUBLIC_API_URL: z.string().url() }, runtimeEnv: process.env }); - Restart the dev server after changing .env files — Next.js does not hot-reload environment variables.
# After editing .env.local or adding new variables: npm run dev # restart required for new vars to take effect
Secrets and Version Control
- Never commit .env.local or .env.production.local to version control.
# .gitignore .env.local .env.development.local .env.test.local .env.production.local - Commit .env.example as documentation of required variables — never put real values in it.
# .env.example (safe to commit) DATABASE_URL=postgresql://user:password@localhost/db NEXT_PUBLIC_API_URL=https://api.example.com AUTH_SECRET= - Never access process.env in a Client Component for secrets — server-only variables return undefined in the browser and do NOT get bundled.
"use client"; // ❌ DATABASE_URL is undefined in the browser — not leaked, just missing const url = process.env.DATABASE_URL; - Use NEXT_PUBLIC_ only for truly public values — these are inlined as string literals and visible to anyone who reads the JS bundle.
# Safe to expose NEXT_PUBLIC_STRIPE_KEY=pk_live_abc123 NEXT_PUBLIC_GA_ID=G-XXXXXXX # Never prefix with NEXT_PUBLIC_ STRIPE_SECRET=sk_live_secret DATABASE_URL=postgres://prod-secret - Set environment variables in your CI/CD platform rather than committing .env.production files.
# Vercel CLI vercel env add DATABASE_URL production # GitHub Actions secret # Settings > Secrets > Actions > New repository secret
FAQ
Prefix the variable name with NEXT_PUBLIC_ (e.g., NEXT_PUBLIC_API_URL) and Next.js will inline its value at build time, making it available in both client and server code. Variables without this prefix are stripped from the client bundle entirely.
.env is for default values shared across environments and can be committed to version control, while .env.local overrides those values locally and should never be committed. Next.js loads both files, but .env.local always takes precedence.
Make sure the variable is defined in your .env.local or .env file and that you are not accidentally prefixing it with NEXT_PUBLIC_ while only expecting server-side access. Also confirm the dev server was restarted after adding or changing the variable, since Next.js does not hot-reload env files.
Yes — use the env key in next.config.js to explicitly expose specific variables to the client, or use serverRuntimeConfig for server-only values and publicRuntimeConfig for values shared with the client at runtime rather than build time.
Next.js loads environment files in priority order from highest to lowest: .env.{NODE_ENV}.local, then .env.local (skipped in test), then .env.{NODE_ENV}, then .env. The first file that defines a variable wins — lower-priority files cannot override it. Use .env.local for secrets that should never be committed, and .env for shared defaults.