Never Hardcode Secrets — Use Environment Variables Properly
Impact: HIGH (prevents credential leaks in source control and client bundles)
Hardcoded API keys, database URLs, and tokens in source code end up in Git history permanently — even if deleted later. In Next.js, any environment variable prefixed with NEXT_PUBLIC_ is embedded in the client bundle and visible to anyone. Use .env.local for secrets, never commit them, and understand which variables are public vs. server-only.
Incorrect (hardcoded secrets):
// ❌ Hardcoded in source — visible in Git history foreverconst stripe = new Stripe('sk_live_abc123realkey456', { apiVersion: '2024-01-01',})// ❌ Database URL in codeconst prisma = new PrismaClient({ datasources: { db: { url: 'postgresql://admin:password@prod-db:5432/myapp' }, },})
Incorrect (wrong env var prefix):
// ❌ NEXT_PUBLIC_ prefix makes this visible in the browser bundle!// .env.localNEXT_PUBLIC_STRIPE_SECRET=sk_live_abc123// ❌ Anyone can see this in the browser's JS sourceconst stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET!)
Correct (server-only env vars):
# .env.local (never committed — in .gitignore)STRIPE_SECRET_KEY=sk_live_abc123DATABASE_URL=postgresql://admin:password@prod-db:5432/myapp# Only public/non-sensitive values get the NEXT_PUBLIC_ prefixNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xyz789NEXT_PUBLIC_APP_URL=https://myapp.com
// ✅ Server-only — only accessible in Server Components, API routes, Server Actionsconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-01-01',})// ✅ Validate env vars at startupimport { z } from 'zod'const envSchema = z.object({ STRIPE_SECRET_KEY: z.string().startsWith('sk_'), DATABASE_URL: z.string().url(), NEXT_PUBLIC_APP_URL: z.string().url(),})export const env = envSchema.parse(process.env)
Ensure .gitignore includes env files:
# .gitignore.env.env.local.env.*.local
Detection hints:
# Find hardcoded secrets (API keys, tokens, passwords)grep -rn "sk_live\|sk_test\|password.*=.*['\"]" src/ --include="*.ts" --include="*.tsx"# Find NEXT_PUBLIC_ vars that look like secretsgrep -rn "NEXT_PUBLIC_.*SECRET\|NEXT_PUBLIC_.*KEY\|NEXT_PUBLIC_.*TOKEN" .env* src/# Check if .env.local is in .gitignoregrep -q "\.env\.local" .gitignore && echo "OK" || echo "WARNING: .env.local not in .gitignore"