Environment Variables

Master environment variables in Next.js — server vs client exposure, .env files, runtime vs build-time variables, and secrets management in production.

Last reviewed: January 2025

·Next.js 12+Next.js 13Next.js 14Next.js 15
✅
Quick Understanding: In Next.js, environment variables are server-only by default. Only variables prefixed with NEXT_PUBLIC_ are sent to the browser. Never put secrets in NEXT_PUBLIC_ variables — they are embedded in the JavaScript bundle and visible to anyone.

🔍 What Is It?

Next.js has two types of environment variables:

  1. Server-only — available in Server Components, Route Handlers, middleware, and build scripts. Never sent to the browser.

- DATABASE_URL, API_SECRET, STRIPE_SECRET_KEY

  1. Public (client-accessible) — prefixed with NEXT_PUBLIC_. Embedded in the JavaScript bundle. Visible to all users.

- NEXT_PUBLIC_API_URL, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

.env files (loaded in order):

  • →.env — all environments
  • →.env.local — local overrides (git-ignored)
  • →.env.development — development only
  • →.env.production — production only

🎯 Why Do We Need It?

Environment variables solve two problems:

  1. Configuration per environment — different database URLs for dev, staging, and production
  2. Secret management — API keys and database credentials that should never be in source code

The NEXT_PUBLIC_ prefix system ensures secrets never accidentally reach the client bundle.

🏗️ What Happens in Real Projects?

Typical .env.local for development:

DATABASE_URL="postgresql://localhost:5432/mydb"
NEXTAUTH_SECRET="dev-secret-change-in-production"
NEXTAUTH_URL="http://localhost:3000"
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
NEXT_PUBLIC_APP_URL="http://localhost:3000"

⚙️ How It Works

Build-time vs runtime:

  • →NEXT_PUBLIC_ variables are embedded at build time. The value at build time is baked into the JS bundle. Changing the variable requires a rebuild.
  • →Server-only variables are read at runtime (when the server function executes). No rebuild needed to change them.

On Vercel: Set environment variables in the Vercel dashboard. They are injected at build time (for NEXT_PUBLIC_) or at runtime (for server-only). Never commit .env.production to git.

💻 Example

Environment Variable Patterns — Type-safe env validation and correct usage in Server vs Client Components.

ts
// lib/env.ts — Validate env vars at startup
import { z } from 'zod';

// Server-only variables
const serverSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
  NODE_ENV: z.enum(['development', 'test', 'production']),
});

// Public variables
const clientSchema = z.object({
  NEXT_PUBLIC_APP_URL: z.string().url(),
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().startsWith('pk_'),
});

// Validate on import — fails fast if vars are missing
export const env = serverSchema.parse(process.env);
export const clientEnv = clientSchema.parse({
  NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
});

🤔 Common Confusions

❓ Can I access server env vars in Client Components?

No — process.env.SECRET_KEY is undefined in Client Components. Only NEXT_PUBLIC_ variables are available. If a Client Component needs a value from a server env var, fetch it from a Route Handler or pass it as a prop from a Server Component.

❓ Are NEXT_PUBLIC_ variables safe for API keys?

Only for public keys (Stripe publishable key, Google Maps API key with domain restrictions). Never for secret keys. NEXT_PUBLIC_ variables are embedded in the JS bundle — anyone can find them in DevTools. Use them only for values that are designed to be public.

❓ Do I need to restart the dev server after changing .env.local?

Yes — environment variables are loaded at server startup. Changes to .env files require restarting next dev. On Vercel, changing an environment variable triggers a new deployment.

🚨 Common Production Issues

Committing .env.local or .env.production to git — immediately rotate all secrets. Add .env*.local and .env.production to .gitignore. Use git-secrets or a pre-commit hook to prevent this.

NEXT_PUBLIC_ variable not updated after change — public variables are baked in at build time. Changing them in the Vercel dashboard requires triggering a new deployment (not just a revalidation).

Missing env vars causing runtime crashes — validate all required env vars at startup with Zod. This catches missing variables immediately on deployment, not during a user request.

✅ Best Practices

Validate all env vars at startup with Zod — fail fast, not during user requests

Never put secrets in NEXT_PUBLIC_ variables

Use .env.local for local development — it is git-ignored by default

Use a secrets manager (AWS Secrets Manager, Vault) for production secrets in enterprise

Document all required env vars in a .env.example file committed to git

🏛️ Architect's Note

Senior Architect's Perspective

Treat environment variables as a configuration contract. Document every variable in .env.example with a description and example value. Validate them at startup with Zod. This makes onboarding new developers trivial and prevents "it works on my machine" issues.

💬 Frequently Asked Questions

How do I use environment variables in next.config.ts?

process.env is available in next.config.ts. You can also use the env config option to expose server vars to the build: env: { MY_VAR: process.env.MY_VAR }. Variables set in env config are available in Server Components without the NEXT_PUBLIC_ prefix.

What is the difference between .env and .env.local?

.env is committed to git and contains non-sensitive defaults. .env.local is git-ignored and contains local overrides (your personal database URL, local API keys). .env.local always takes precedence over .env.

How do I share env vars between multiple Next.js apps in a monorepo?

Create a shared .env file at the monorepo root and reference it in each app's next.config.ts using envDir. Or use a secrets manager and have each app fetch its own secrets at startup.

🎯 Key Takeaways

1Server-only vars: accessible in Server Components, Route Handlers, middleware
2NEXT_PUBLIC_ vars: embedded in JS bundle, visible to all users — never put secrets here
3Validate all env vars at startup with Zod — fail fast
4.env.local is git-ignored — use for local development secrets
5Document all vars in .env.example — commit this file to git
6NEXT_PUBLIC_ vars require a rebuild to update — they are baked in at build time

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Next.js references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Next.js learning path

Browse the full Next.js topic collection and learning order on SplashRide.

Next.js Interview Prep

Practice Next.js interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Vercel Deployment

Deploy Next.js to Vercel — zero-config deployments, preview environments, edge functions, analytics, and the production configuration patterns used in enterprise.

Authentication

Implement production-grade authentication in Next.js — NextAuth v5, JWT strategies, session management, protected routes, and the multi-layer auth architecture used in enterprise apps.