The Difference Between .env, .env.local, and .env.production
I still see PRs where someone renames .env.local to .env.production and calls it done. The two files load at different times, get treated differently by every framework below, and mixing them up is how test credentials end up live.
The .env File Family
Five files, five jobs. The table below is the version I wish I'd had pinned above my desk during my first few Next.js projects:
| File | Purpose | Commit to Git? |
|---|---|---|
| .env | Default values for all environments | Sometimes (with safe defaults) |
| .env.local | Local machine overrides with secrets | Never |
| .env.development | Development-specific values | Sometimes |
| .env.production | Production-specific values | Rarely (prefer platform settings) |
| .env.example | Template with placeholder values | Always |
Next.js Environment Files
Next.js loads four tiers of .env files in a fixed order, and whichever loads last wins for any variable defined in more than one:
Key Rules for Next.js
.env.localis NOT loaded duringnext build- use .env.production.local instead- Test environment uses .env.test (not .env.local to ensure consistent test behavior)
- Only variables prefixed with
NEXT_PUBLIC_are exposed to the browser - Server-side variables are available in API routes, getServerSideProps, etc.
Example Setup
Vite Environment Files
Vite uses a similar system but with the VITE_ prefix instead of NEXT_PUBLIC_:
Accessing Variables in Vite
Custom modes can be used with vite build --mode stagingwhich will load .env.staging.
Create React App
CRA uses the REACT_APP_ prefix and has a simpler loading order:
Note: CRA embeds environment variables at build time, not runtime. Changing .env files requires a rebuild.
Best Practices
Keep Secrets Out of Version Control
Commit only the files that hold no real secrets. Document what's missing with .env.example instead:
Let .env.local Hold Your Personal Setup
Your .env.local can point at your own database, your own test API keys, whatever your machine needs, without touching what teammates have configured on theirs.
Leave .env.production for Non-Secrets
Route real production values through your host's environment variable settings rather than a .env.production file. That gets you:
- Encryption at rest
- Access controls and audit logging
- No risk of accidental commits
- Easy rotation without code changes
Document Everything in .env.example
Maintain one .env.example that lists every required variable with a comment on where each value comes from:
Common Mistakes to Avoid
- Relying on .env.local in CI/CD: most frameworks skip it in test and build environments, so a pipeline that only works locally is usually missing a .env.production.local or platform variable it never needed on your machine.
- Forgetting the client prefix: a variable without NEXT_PUBLIC_, VITE_, or REACT_APP_ stays server-only no matter how correct the value is, and the browser never sees it.
- Expecting a save to update production:most frameworks bake env vars into the build. Edit .env.production after deploying and you're editing a file nobody reads until the next build.
- Mixing secrets into public files: a server-only secret dropped into the same file as your NEXT_PUBLIC_ variables is one typo away from shipping to the browser.
Quick Reference
When you can't remember which file does what, this is the shortcut version of the table above:
- .envholds safe defaults you don't mind committing.
- .env.local holds your secrets and never gets committed.
- .env.development / .env.production hold values specific to one environment, still no secrets.
- .env.example documents the shape for everyone else and always gets committed.
- Real production secrets live in your host's dashboard, not in a file with a dot in front of its name.