Back to Blog
Basics8 min readJanuary 10, 2024

The Difference Between .env, .env.local, and .env.production

Modern frameworks support multiple .env file variants for different purposes. Understanding when to use each one is essential for proper configuration management.

The .env File Family

Before we dive into the specifics, let's understand the general purpose of each file type:

FilePurposeCommit to Git?
.envDefault values for all environmentsSometimes (with safe defaults)
.env.localLocal machine overrides with secretsNever
.env.developmentDevelopment-specific valuesSometimes
.env.productionProduction-specific valuesRarely (prefer platform settings)
.env.exampleTemplate with placeholder valuesAlways

Next.js Environment Files

Next.js has one of the most comprehensive .env file systems. Files are loaded in a specific order, with later files overriding earlier ones:

# Loading order (top = loaded first, bottom = highest priority)
1. .env
2. .env.local
3. .env.development / .env.production / .env.test
4. .env.development.local / .env.production.local / .env.test.local

Key Rules for Next.js

  • .env.local is NOT loaded during next 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

# .env (committed - safe defaults)
NEXT_PUBLIC_SITE_NAME=My App
NEXT_PUBLIC_API_URL=https://api.example.com
# .env.local (never committed - your secrets)
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
AUTH_SECRET=your-super-secret-key
STRIPE_SECRET_KEY=sk_test_xxx
# .env.development (committed - dev-specific non-secrets)
NEXT_PUBLIC_API_URL=http://localhost:3001
LOG_LEVEL=debug

Vite Environment Files

Vite uses a similar system but with the VITE_ prefix instead of NEXT_PUBLIC_:

# Loading order
1. .env
2. .env.local
3. .env.[mode] (e.g., .env.development, .env.production)
4. .env.[mode].local

Accessing Variables in Vite

// In your Vite application
const apiUrl = import.meta.env.VITE_API_URL;
const mode = import.meta.env.MODE; // 'development' or 'production'
const isDev = import.meta.env.DEV; // boolean
const isProd = import.meta.env.PROD; // boolean

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:

# npm start (development)
.env.development.local
.env.local
.env.development
.env
# npm run build (production)
.env.production.local
.env.local
.env.production
.env

Note: CRA embeds environment variables at build time, not runtime. Changing .env files requires a rebuild.

Best Practices

1. Keep Secrets Out of Version Control

Only commit files that don't contain actual secrets. Use .env.example for documentation:

# .gitignore
.env.local
.env.*.local
.env.development
.env.production

2. Use .env.local for Personal Overrides

Each developer can have their own .env.local with personal settings (different database, test API keys, etc.) without affecting teammates.

3. Don't Use .env.production for Real Secrets

For production deployments, use your hosting platform's environment variable settings instead of .env.production files. This provides:

  • Encryption at rest
  • Access controls and audit logging
  • No risk of accidental commits
  • Easy rotation without code changes

4. Document Everything in .env.example

Always maintain a .env.example file that lists all required variables with comments explaining their purpose:

# .env.example
# Database
# Format: postgres://USER:PASSWORD@HOST:PORT/DATABASE
DATABASE_URL=postgres://localhost:5432/myapp
# Authentication (generate with: openssl rand -base64 32)
AUTH_SECRET=replace-with-random-string
# Stripe (get from https://dashboard.stripe.com/apikeys)
STRIPE_SECRET_KEY=sk_test_xxx
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx

Common Mistakes to Avoid

  • Using .env.local in CI/CD: Most frameworks skip .env.local in test/build environments. Use .env.production.local or platform-specific variables instead.
  • Forgetting the prefix:Client-side variables must use the correct prefix (NEXT_PUBLIC_, VITE_, REACT_APP_) or they won't be available in the browser.
  • Expecting runtime updates: Most frameworks embed env vars at build time. Changing .env requires a rebuild for changes to take effect.
  • Mixing secrets with public vars: Keep secret server-only variables separate from public client-side variables to avoid accidental exposure.

Key Takeaways

  • .env - Base configuration with safe defaults, often committed
  • .env.local - Personal overrides with secrets, never committed
  • .env.development/.env.production - Environment-specific values
  • .env.example - Template documentation, always committed
  • Later files override earlier files in the loading order
  • Use platform settings for production secrets, not .env files