Environment Variables in Next.js: Complete Guide
Asad Ali
Founder & Lead Developer · Former WordPress Core Contributor
Environment Variables in Next.js: Complete Guide
Environment variables store configuration that varies between environments—API keys, database URLs, feature flags. Next.js handles them with some important security considerations.
Basic Concept
Environment variables let you configure your app without changing code:
// Instead of
const apiKey = "sk-abc123"; // ❌ Hardcoded
// Use
const apiKey = process.env.API_KEY; // ✅ From environment
.env Files
File Types
| File | Purpose | Git |
.env | Default for all environments | ❌ Ignore |
.env.local | Local overrides | ❌ Ignore |
.env.development | Development only | Optional |
.env.production | Production only | Optional |
.env.example | Template for team | ✅ Commit |
Loading Priority
1. .env.local (highest priority)
2. .env.development or .env.production
3. .env (lowest priority)
Server vs Client Variables
Server-Only (Default)
.env.local
DATABASE_URL=postgres://localhost/mydb
API_SECRET=super_secret_key
// Only works in server components, API routes, getServerSideProps
const dbUrl = process.env.DATABASE_URL;
Never exposed to browser. Used for:
- Database connections
- API secrets
- Private keys
Client-Accessible
Prefix with NEXT_PUBLIC_:
.env.local
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-XXXXX
// Works everywhere, including browser
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
⚠️ These are bundled into your JavaScript and visible to anyone.
When to Use Which
Use Server Variables For:
- Database credentials
- API keys with write access
- Payment processor secrets
- Email service credentials
- OAuth client secrets
Use NEXT_PUBLIC_ For:
- Public API keys (e.g., Google Maps)
- Analytics IDs
- Site configuration
- Feature flags visible in UI
- Public URLs
Examples
.env.local
Database
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
Authentication
AUTH_SECRET=random-32-character-secret-here
GITHUB_CLIENT_ID=abc123
GITHUB_CLIENT_SECRET=secret123
Email (server-side)
RESEND_API_KEY=re_123456789
Public (client-safe)
NEXT_PUBLIC_SITE_URL=https://mysite.com
NEXT_PUBLIC_GA_ID=G-XXXXXXXX
.env.example
Commit this as a template:
Database
DATABASE_URL=
Authentication
AUTH_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
Email
RESEND_API_KEY=
Public
NEXT_PUBLIC_SITE_URL=
NEXT_PUBLIC_GA_ID=
Using in Code
Server Components
// app/page.tsx (Server Component by default)
export default function Page() {
const secret = process.env.API_SECRET; // ✅ Works
return
...;
}
Client Components
'use client';
export default function Analytics() {
const gaId = process.env.NEXT_PUBLIC_GA_ID; // ✅ Works (NEXT_PUBLIC_)
const secret = process.env.API_SECRET; // ❌ Undefined (not NEXT_PUBLIC_)
return
...;
}
API Routes
// app/api/users/route.ts
export async function GET() {
const db = process.env.DATABASE_URL; // ✅ Works
// Use db...
}
Config Files
// next.config.ts
const config = {
env: {
customKey: process.env.CUSTOM_KEY, // ✅ Works at build time
},
};
export default config;
Deployment
Vercel
1. Go to Project → Settings → Environment Variables
2. Add each variable
3. Choose environments (Production, Preview, Development)
Variable: DATABASE_URL
Value: postgres://...
Environments: ☑️ Production ☑️ Preview
Netlify
1. Site settings → Environment variables
2. Add key-value pairs
Docker
Dockerfile
ENV DATABASE_URL=postgres://...
Or at runtime
docker run -e DATABASE_URL=postgres://... my-app
Other Platforms
Most platforms have environment variable settings. Check their docs.
TypeScript Typing
Add types for autocomplete and safety:
// env.d.ts (or types/env.d.ts)
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_SECRET: string;
NEXT_PUBLIC_SITE_URL: string;
NEXT_PUBLIC_GA_ID?: string; // Optional
}
}
Now TypeScript knows your env vars.
Validation
Check required variables at startup:
// lib/env.ts
function getEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(Missing environment variable: ${key});
}
return value;
}
export const env = {
DATABASE_URL: getEnv('DATABASE_URL'),
API_SECRET: getEnv('API_SECRET'),
SITE_URL: process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
};
Or use a library like zod:
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_SECRET: z.string().min(32),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = envSchema.parse(process.env);
Security Best Practices
1. Never Commit Secrets
.gitignore
.env
.env.local
.env*.local
2. Use .env.example
Commit a template without values:
.env.example
DATABASE_URL=postgres://localhost/dev
API_KEY=your-api-key-here
3. Rotate Compromised Secrets
If a secret leaks:
1. Generate new secret immediately
2. Update in deployment platform
3. Revoke old secret
4. Investigate how it leaked
4. Minimal Permissions
Give API keys only the permissions they need.
5. Audit Regularly
Periodically review what secrets you have and if they're still needed.
Common Issues
"process.env is undefined"
Client-side without NEXT_PUBLIC_ prefix:
// ❌ Won't work in browser
const secret = process.env.SECRET;
// ✅ Works in browser
const publicKey = process.env.NEXT_PUBLIC_KEY;
"Values not updating"
Restart the dev server after changing .env files:
Ctrl+C then:
npm run dev
"Works locally, fails in production"
Make sure variables are set in your deployment platform, not just .env.local.
"Variable is empty string"
Check for typos, missing values, or wrong environment selection in deployment.
Quick Reference
| Scenario | Use | Available Where |
| Database URL | DATABASE_URL | Server only |
| API Secret | API_SECRET | Server only |
| Public site URL | NEXT_PUBLIC_SITE_URL | Everywhere |
| Analytics ID | NEXT_PUBLIC_GA_ID | Everywhere |
Conclusion
Environment variables in Next.js:
1. Server variables - Default, secure, never exposed
2. NEXT_PUBLIC_* - Client-accessible, for public config
3. .env.local - Local development (gitignored)
4. .env.example - Template for team (committed)
5. Platform settings - Production values
Keep secrets secret. Use NEXT_PUBLIC_ only when truly needed. Validate on startup.
Related guides:
Related Articles
View allGit for WordPress Developers: Getting Started
Learn Git version control coming from WordPress. Track changes, collaborate, and never lose work again.
TypeScript for JavaScript Developers: A Practical Guide
Learn TypeScript to write safer code. Types, interfaces, and practical patterns that make JavaScript development better.
Tailwind CSS for WordPress Developers: Complete Guide
Learn Tailwind CSS coming from WordPress. Utility-first CSS explained for theme developers making the switch to modern frameworks.
React vs Vue vs Svelte for WordPress Developers
Confused by JavaScript frameworks? This guide explains React, Vue, and Svelte from a WordPress developer's perspective.