Environment Variables in Next.js
Next.js automatically loads environment variables from .env, .env.local, .env.development, and .env.production files at the project root, with .env.local always taking precedence and being the recommended place for secrets since it's git-ignored by default in the standard create-next-app scaffold. Variables defined this way are available in process.env inside server-side code — Server Components, Route Handlers, getServerSideProps — but are stripped out of the client-side JavaScript bundle unless explicitly exposed.
Cricket analogy: The .env.local file taking precedence over other .env files is like a team's final matchday team sheet overriding the provisional squad announced earlier in the week — the last, most specific list wins.
Server-Only vs. NEXT_PUBLIC_ Variables
Any variable you want accessible in client-side (browser) code — for example, in a Client Component that calls a public API URL — must be prefixed with NEXT_PUBLIC_, such as NEXT_PUBLIC_API_URL. During the build, Next.js's compiler statically replaces references to process.env.NEXT_PUBLIC_API_URL with the actual string value, inlining it directly into the bundle; variables without that prefix (like a database connection string or an API secret key) simply resolve to undefined if you try to read them in browser code, because they were never included in the client bundle in the first place.
Cricket analogy: The NEXT_PUBLIC_ prefix is like a player's public jersey number visible to every fan in the stadium, while their private medical and fitness reports (server-only secrets) never leave the team's internal management system.
# .env.local (git-ignored, safe for real secrets)
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_live_51H...
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=G-ABC123XYZ// app/api/checkout/route.ts (server-only: secret is safe here)
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const session = await stripe.checkout.sessions.create({ /* ... */ });
return Response.json({ url: session.url });
}
// app/components/Analytics.tsx ('use client': only NEXT_PUBLIC_ vars work here)
'use client';
export default function Analytics() {
const gaId = process.env.NEXT_PUBLIC_GA_ID; // inlined at build time
return <script data-ga-id={gaId} />;
}Never prefix a genuinely secret value (API keys, database credentials, signing secrets) with NEXT_PUBLIC_ — doing so bakes it directly, in plaintext, into the JavaScript bundle shipped to every visitor's browser, where it can be trivially extracted from browser dev tools.
Environment-Specific Files and Deployment
Next.js resolves environment files in a defined precedence order: .env.$(NODE_ENV).local, then .env.local (skipped during next build's test phase), then .env.$(NODE_ENV), then .env, with earlier files winning over later ones for the same key. In production deployments — Vercel, Docker, or a custom Node server — you typically don't ship .env.local at all; instead, secrets are injected as actual environment variables through the hosting platform's dashboard or CI/CD secret store, and Next.js reads them from process.env exactly the same way regardless of whether they came from a file or the platform's runtime.
Cricket analogy: Environment file precedence resolving from most to least specific is like DRS reviews: the on-field umpire's initial call can be overridden by the third umpire's more specific replay review, which itself can be constrained by the match referee's final ruling on protocol.
You can inspect exactly which env values Next.js will inline into the client bundle by checking next.config.js's env key (for legacy manual exposure) alongside any NEXT_PUBLIC_-prefixed variables — but for most apps in modern Next.js, the NEXT_PUBLIC_ prefix convention alone is sufficient and the manual env key is rarely needed.
- Next.js auto-loads .env, .env.local, .env.development, and .env.production, with .env.local taking highest non-environment-specific precedence.
- Only variables prefixed with NEXT_PUBLIC_ are inlined into the client-side JavaScript bundle at build time.
- Server-only variables (no prefix) resolve to undefined if accessed from browser/Client Component code.
- .env.local is git-ignored by default in create-next-app scaffolds and is the recommended place for local secrets.
- Never prefix real secrets with NEXT_PUBLIC_, since that bakes them in plaintext into the public bundle.
- In production, secrets are typically injected via the hosting platform's environment variable dashboard rather than shipped in a file.
- Next.js reads process.env identically whether the value came from a .env file or the platform's runtime environment.
Practice what you learned
1. What prefix must an environment variable have to be accessible in client-side (browser) code?
2. What happens if you try to read a non-prefixed environment variable inside a Client Component?
3. Which .env file is git-ignored by default in a standard create-next-app project and recommended for local secrets?
4. Why is it dangerous to prefix a real API secret key with NEXT_PUBLIC_?
5. How are secrets typically provided in a production deployment, rather than via a committed .env.local file?
Was this page helpful?
You May Also Like
Static Assets and the public Folder
How Next.js serves files from the public directory at the site root, and when to use it versus importing assets directly into components.
Styling Approaches in Next.js
A practical comparison of the main ways to style Next.js apps: global CSS, CSS Modules, Tailwind CSS, and CSS-in-JS with styled-jsx.
Image Optimization with next/image
How the built-in Next.js Image component automatically resizes, lazy-loads, and serves modern image formats to improve performance.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics