Concept
Twelve-Factor App Configuration
According to the Twelve-Factor App methodology, an application's configuration must be stored in the environment, completely separate from the application code. This allows the exact same code build to run in local development, staging, and production by changing environment variables.
In Node.js, environment variables are accessed via process.env.VARIABLE_NAME.
Environment Profiles
We structure configuration settings across separate files:
.env.development: Variables for local development..env.test: Overrides for test suites (like mocking database URLs)..env.production: Variables for the production build. This file should never be committed to Git.
Validating Environment Variables using Zod
Accessing process.env attributes directly in client code risks runtime failures if a required variable is missing. Validate variables at startup using a schema parser:
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.string().transform(Number).default('3000'),
NODE_ENV: z.enum(['development', 'production', 'test'])
});
// Throws immediate descriptive error on startup if variables are invalid
export const env = envSchema.parse(process.env);Next.js Client vs Server Environment Variables
Next.js prevents server-side credentials (like private database passwords) from leaking into public JavaScript client bundles:
- Server-Only (Default): Accessed only inside Server Components or API routes.
- Client-Public: Variables prefixed with
NEXT_PUBLIC_(e.g.NEXT_PUBLIC_ANALYTICS_ID) are bundled and exposed to the browser.
Common Mistakes
1. Committing .env secret files containing production credentials to Git
Committing API keys, database connection strings, or encryption passwords to source control exposes them permanently to anyone with read access. Always list .env, .env.local, and .env.production inside .gitignore.
2. Prefixer leaks on sensitive variables in Next.js
Adding the NEXT_PUBLIC_ prefix to a database connection string or private Stripe API key bundles the secret value into browser-facing JS bundles. Only prefix public configuration variables.
Best Practices
- Never Commit Secrets: Ensure all
.envfiles containing credentials are added to.gitignore. - Validate on Boot: Parse and validate
process.envvariables using Zod schemas at startup to fail fast if configurations are missing. - Use Secrets Managers: For production servers, load variables using secrets managers (like AWS Secrets Manager, Vercel Secrets, or HashiCorp Vault) rather than static files.
