Environment Management
Environment Variables & Secrets Management
Learn how to manage configuration that changes between environments and keep secrets out of your codebase.
What Environment Variables Are
Environment variables are configuration values that live outside your code. They let the same codebase behave differently in different environments without changing a single line of source code.
Instead of this (dangerous):
const apiKey = 'sk-abc123-never-do-this';You do this:
const apiKey = process.env.OPENAI_API_KEY;The value comes from the environment, not the code. Different environments provide different values.
The .env File Hierarchy
Most JavaScript projects use a hierarchy of .env files:
.env # Committed: shared defaults for all environments
.env.local # Git-ignored: local overrides (your personal API keys)
.env.development # Development-specific values
.env.staging # Staging-specific values
.env.production # Production-specific values
.env.test # Test-specific valuesFiles later in the resolution order override earlier ones. Local files (*.local) are always git-ignored and never committed.
NEVER Commit Secrets to Git
This is the #1 rookie mistake, and it is permanent. Git history is forever. Once a secret is committed — even if you delete it in the next commit — it exists in the history and must be considered compromised.
Real consequences of leaked secrets:
- AWS keys burned through $50,000 in cloud bills in a few hours (crypto mining)
- OpenAI API keys used to generate thousands of API calls, destroying your budget
- Database credentials used to exfiltrate all user data
Tools to prevent this:
- git-secrets — scans commits for common secret patterns
- truffleHog — scans git history for high-entropy strings
- GitHub Secret Scanning — automatically alerts on detected secrets in public repos
Add a pre-commit hook that blocks secrets before they can be committed.
What Belongs in Environment Variables
Good candidates for environment variables:
| Category | Examples |
|---|---|
| API Keys | OPENAI_API_KEY, STRIPE_SECRET_KEY, SENDGRID_API_KEY |
| Database | DATABASE_URL, REDIS_URL, DB_PASSWORD |
| Service Endpoints | API_BASE_URL, WEBHOOK_URL |
| Feature Flags | FEATURE_NEW_CHECKOUT, ENABLE_AI_FEATURES |
| Configuration | LOG_LEVEL, MAX_UPLOAD_SIZE, RATE_LIMIT_RPM |
| AI Model Selection | OPENAI_MODEL, AI_TEMPERATURE, MAX_TOKENS |
What does NOT belong in environment variables:
- Business logic
- UI text or copy
- Anything that needs version control or code review
Next.js Specifics: Public vs Secret
In Next.js, environment variables are server-only by default. To expose a variable to the browser, prefix it with NEXT_PUBLIC_:
// Server-only (safe for secrets)
process.env.DATABASE_URL
process.env.STRIPE_SECRET_KEY
// Client-accessible (never put secrets here)
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
process.env.NEXT_PUBLIC_SUPABASE_URLNever put API keys or database credentials in NEXT_PUBLIC_ variables. They are bundled into the JavaScript sent to the browser and visible to anyone who inspects the page source.
Secrets Management Tools
For team environments, a dedicated secrets manager is essential:
- Vercel Environment Variables — built into Vercel, syncs to deployments, supports per-environment values
- AWS Secrets Manager — enterprise-grade, rotates secrets automatically, IAM-controlled access
- HashiCorp Vault — self-hosted, highly configurable, supports dynamic secrets
- Doppler — developer-friendly, syncs to all platforms, provides a CLI for local development
- 1Password for Teams — stores secrets in your existing password manager, developer-friendly CLI
How to Rotate Compromised Secrets
If you suspect a secret was exposed:
- Immediately revoke the compromised key — in the service's dashboard (AWS Console, OpenAI, Stripe, etc.)
- Generate a new key — treat the old one as permanently compromised
- Update all environments — development, staging, production
- Scan git history — use truffleHog to check if the key appears in history
- Audit usage — check the service's access logs for unauthorized use
- Notify if required — some data breaches require regulatory notification (GDPR, SOC 2)
Key Takeaways
- Environment variables separate configuration from code — the same codebase, different behavior per environment
- Never commit secrets to git — the history is permanent and public repositories are searchable
- Use NEXT_PUBLIC_ prefix only for values safe to expose to browsers
- Production secrets belong in a dedicated secrets manager, not .env files on servers
- Rotate compromised secrets immediately — revoke first, replace second, audit third
Example
# .env.local (git-ignored — personal overrides)
DATABASE_URL=postgresql://localhost:5432/myapp_dev
OPENAI_API_KEY=sk-your-dev-key-here
# Next.js: server-only vs public variables
# Server-only (secret):
DATABASE_URL=postgresql://...
OPENAI_API_KEY=sk-...
STRIPE_SECRET_KEY=sk_test_...
# Client-accessible (never secrets):
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...