Home โบ Tech Tutorials โบ Environment Variables: The Complete Guide for Developers
๐ PinnedEnvironment VariablesSecurityDevOpsBest Practices๐ฅ Hot
Environment Variables: The Complete Guide for Developers
ยท ยท 3824 views ยท 30 replies ยท 3 min read
Environment variables connect your code to the outside world โ database URLs, API keys, feature flags. Misconfiguring them is one of the most common causes of production incidents and security breaches. Here's the complete guide to managing them correctly.
The Hierarchy of Config
Layer
Where
Example
Never Commit?
Default values
Code (as fallback)
PORT ?? 3000
Commit (with safe defaults)
Local dev overrides
.env.local
DATABASE_URL=localhost
Yes (.gitignore)
CI/CD
Platform secrets
DATABASE_URL=staging-db
Yes (platform-managed)
Production
Platform secrets / vault
DATABASE_URL=prod-db
Yes (platform-managed)
Public config
NEXT_PUBLIC_* vars
NEXT_PUBLIC_API_URL
OK (intentionally public)
Rules for Environment Variables
Never commit secrets to Git. Use .gitignore for .env.local, .env.*.local. If a secret ever hits Git history, rotate it immediately.
Prefix public variables. Next.js uses NEXT_PUBLIC_*. Vite uses VITE_*. This makes it clear what's exposed to the browser.
Validate at startup, not at runtime. Use Zod to validate all env vars when the app starts. If a required var is missing, crash immediately โ don't fail mysteriously 3 hours later.
Use different values per environment. Development, staging, and production should have separate database URLs, API keys, and feature flags.
Validation Pattern (Prevent Runtime Surprises)
// env.ts โ validate all env vars at startup
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
NEXT_PUBLIC_APP_URL: z.string().url().default("http://localhost:3000"),
FEATURE_NEW_CHECKOUT: z.enum(["true", "false"]).default("false"),
});
export const env = envSchema.parse(process.env);
// If any var is missing or invalid, the app crashes immediately
Managing Secrets Across a Team
Tool
Best For
How It Works
Doppler
Teams, automatic sync
Central dashboard โ CLI syncs to local .env. Secrets never on disk.
Infisical
Open source, self-hosted
Self-hosted Doppler alternative. Inject secrets at build/run time.
1Password CLI
Small teams with 1Password
op run --env-file=.env -- npm run dev. Secret references, not values.
Platform-native
Simplest, free
Vercel/Render/Railway all have secret management built in.
Common Mistakes & Fixes
Mistake
Fix
Hardcoding API keys in source
Move to .env.local immediately. Check git history. Rotate if exposed.
NEXT_PUBLIC_* for secrets
NEXT_PUBLIC_ vars are bundled into client JS. Anyone can see them. Never put secrets here.
Same API key for dev + prod
Use separate keys. Stripe has test mode keys. Dev databases are separate.
.env.example not updated
Add new vars to .env.example with dummy values. Treat it as documentation.
Secrets in Docker images
Inject at runtime, not at build time. Use docker run -e or Docker secrets.
Bottom line: Validate env vars at startup with Zod. Never put secrets in NEXT_PUBLIC_* or Git. Use Doppler/Infisical for teams, platform-native for side projects. Document every variable in .env.example. See also: Web Security Basics and Error Handling Best Practices.
Enjoy this article? Share your thoughts, questions, or experiences in the comments below โ your insights help other readers too.
Join the discussion โ