Next.js Environment Variables: The Complete Guide
Environment variables in Next.js have a rule that trips up nearly every developer at least once: the NEXT_PUBLIC_ prefix is the entire difference between a secret staying secret and being shipped to every visitor's browser.
The Two Types
# .env.local
DATABASE_URL=postgres://... # server-only, never reaches the browser
NEXT_PUBLIC_ANALYTICS_ID=UA-12345 # bundled into client JavaScript, visible to anyoneAnything without NEXT_PUBLIC_ is only accessible in server components, Route Handlers, and Server Actions. Anything with the prefix gets compiled directly into your client-side JavaScript bundle — viewable by opening browser dev tools on any deployed site.
Mistake 1: Putting Secrets in NEXT_PUBLIC_ Variables
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_... # catastrophic mistakeThis exposes your Stripe secret key to literally anyone who opens dev tools. Only ever prefix genuinely public values — analytics IDs, publishable API keys, feature flags meant to be visible.
Mistake 2: Expecting Server Variables in Client Components
'use client';
export default function Widget() {
console.log(process.env.DATABASE_URL); // undefined — always, by design
}This isn’t a bug — it’s the entire point. If you need a value client-side, it must be explicitly prefixed and intentionally public.
Mistake 3: Not Restarting the Dev Server After Changing .env
Next.js reads environment variables at build/start time, not on every request in development. Adding a new variable and expecting it to appear without restarting next dev is a common source of "why is this undefined" confusion.
Get Syamkumar S’s stories in your inbox
Join Medium for free to get updates from this writer.
Setting Up Environment Files Correctly
.env.local # local secrets, gitignored, loaded everywhere
.env.development # dev-specific defaults, can be committed
.env.production # production-specific defaults, can be committed.env.local should always be in .gitignore — it's the file real secrets go in, and it should never be committed regardless of how small the project is.
Validating Environment Variables at Startup
A common production incident: a missing environment variable causes a cryptic failure deep in your app instead of a clear error at startup. Validate early:
// lib/env.js
const required = ['DATABASE_URL', 'OPENAI_API_KEY'];for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}Import this file once at the top of your app’s entry point so missing config fails loudly and immediately, not three requests into production traffic.
Using Environment Variables Across Deployment Platforms
Local .env.local files don't automatically transfer to Vercel, Netlify, or any hosting platform — each requires setting the same variables in its own dashboard or CLI. A common deployment bug is a feature working locally and breaking in production simply because a variable was never added to the hosting platform's environment settings.
Level Up Your Next.js Skills
- Next.js for Beginners — free, start-to-finish roadmap for learning Next.js
- AI Apps with Next.js — build production-ready AI features into a real app (premium)
- Next.js Migration — move legacy React apps and Pages Router projects to the App Router (premium)
Bottom Line
The NEXT_PUBLIC_ prefix is the one rule that matters most — everything else about Next.js environment variables follows from understanding that boundary. Validate required variables at startup, keep .env.local out of version control, and remember that local environment files never automatically sync to your hosting platform.








