TL;DR
- Use dotenv for solo projects, prototypes, and small teams.
- Use a config library (
node-config,convict,zod-env,t3-env,envalid) the moment you have > 15 env vars, multiple deploy targets, or a type-safe codebase. - They're not mutually exclusive. Most teams use both:
.envstays the source of values; a config library adds validation and types on top.
What dotenv does
dotenv does one thing — it parses .env and pushes keys into process.env. Nothing else. No validation, no types, no defaults, no schema. It's ~40 lines of real code, which is exactly why it's so widely adopted.
What config libraries add
Every config library layers some combination of:
- Validation: required keys, allowed values, format checks.
- Types: coerce
"3000"→3000,"true"→true. - Defaults: fall back to a value when the variable is missing.
- Schema: a single source of truth for what config exists.
- Fail-fast: crash at startup instead of later, at runtime.
node-config
Hierarchical YAML/JSON files (default.yaml → development.yaml → production.yaml) merged at boot. Overlays on top of env vars. Popular in older Node stacks. Heavier than zod-env for modern TypeScript.
convict
Mozilla-made. You define a schema with types, formats, and env-var mappings. Strong for multi-datacenter apps with deep config trees. Verbose compared to Zod-based libraries.
envalid
Minimal. envalid.cleanEnv(process.env, { PORT: envalid.port() }). Returns a typed object. Great if you already have dotenv and just want validation.
zod-env / t3-env
Modern TypeScript answer. You write a Zod schema, and you get a fully-typed env object. t3-env adds client/server separation for Next.js. These are the default choice for new TS projects.
Decision matrix
| Concern | dotenv only | dotenv + config lib |
|---|---|---|
| Tiny project | ✓ | overkill |
| TypeScript codebase | no types | ✓ |
| > 15 env vars | painful | ✓ |
| Multi-env (dev/staging/prod) | fine | ✓ |
| Fail-fast validation | no | ✓ |
| Onboarding new devs | .env.example | schema |
| Serverless / edge | ✓ | ✓ (zod-env) |
Recommended stack (2026)
- Keep
.envas the raw source of values. - Commit
.env.example— generate it from .env. - Layer
t3-env(orzod-env) for type-safety. - Validate every new env var in CI by running the app with a stripped
.env.
What to do now
If you're already on dotenv and the missing-key errors have started piling up, add a config library today — it's a one-hour migration. If you're starting fresh, go straight to t3-env on Next.js or zod-env elsewhere.
For everything that happens before parsing — catching syntax errors, duplicates, empty values — run your file through the validator. And for syncing across environments, the diff checker is faster than any CLI.