C.W.K.
Stream
Lesson 07 of 09 · published

Environment Variables

~18 min · env, NEXT_PUBLIC_, secrets

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Two kinds of variable

PrefixVisibility
(no prefix)Server-only. Read at runtime. Never reaches the browser.
NEXT_PUBLIC_Inlined into the client bundle at build time. Visible in DevTools.

The build-time gotcha

NEXT_PUBLIC_* values are baked in when you run next build. Re-deploying the same Docker image to staging and prod gives you the same values; you can't change them by setting an env var on the running container.

Loading order

  1. process.env (already-set values win)
  2. .env.production.local / .env.development.local
  3. .env.local (skipped in test)
  4. .env.production / .env.development
  5. .env

Code

.env.local·bash
# Server-only — never exposed
DATABASE_URL=postgresql://localhost:5432/myapp
AUTH_SECRET=change-me
STRIPE_SECRET_KEY=sk_test_xxx

# Client-safe (build-time inlined)
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_STRIPE_KEY=pk_test_xxx
Server vs Client access·tsx
// Server Component — sees everything
export default async function Page() {
  const dbUrl = process.env.DATABASE_URL;        // ✅
  const appUrl = process.env.NEXT_PUBLIC_APP_URL; // ✅
}

// Client Component — only NEXT_PUBLIC_*
'use client';
export function Stripe() {
  const key = process.env.NEXT_PUBLIC_STRIPE_KEY; // ✅
  // process.env.DATABASE_URL — undefined here
}

External links

Exercise

Audit your .env files. List every NEXT_PUBLIC_* variable and confirm none of them is a secret. Move any leaks server-side and refactor the calling code to read them in a Server Component.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.