Managing environment variables in Next.js projects...
Next.js has specific rules for environment variable exposure:
NEXT_PUBLIC_NODE_ENV, VERCEL_* are automatically available.env.local # Local overrides (gitignored)
.env.development # Development defaults
.env.production # Production defaults
.env # Defaults (committed)
# .env.local
DATABASE_URL=postgresql://...
API_SECRET_KEY=secret123
STRIPE_SECRET_KEY=sk_live_...
Access in Server Components, API Routes, Server Actions:
// ✅ Server Component
export default async function Page() {
const dbUrl = process.env.DATABASE_URL;
// Only accessible on server
}
// ✅ API Route
export async function GET() {
const apiKey = process.env.API_SECRET_KEY;
return NextResponse.json({ data: 'secret' });
}
// ✅ Server Action
'use server';
export async function createOrder() {
const stripeKey = process.env.STRIPE_SECRET_KEY;
// Use stripeKey
}
# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My App
NEXT_PUBLIC_ANALYTICS_ID=UA-123456
Access in Client Components:
'use client';
export default function ClientComponent() {
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
// Available in browser (bundled at build time)
return <div>API: {apiUrl}</div>;
}
// env.d.ts or types/env.d.ts
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_SECRET_KEY: string;
NEXT_PUBLIC_API_URL: string;
NEXT_PUBLIC_APP_NAME: string;
}
}
// lib/env.ts
function getEnvVar(name: string, required = true): string {
const value = process.env[name];
if (required && !value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value || '';
}
export const env = {
databaseUrl: getEnvVar('DATABASE_URL'),
apiSecretKey: getEnvVar('API_SECRET_KEY'),
publicApiUrl: getEnvVar('NEXT_PUBLIC_API_URL'),
publicAppName: getEnvVar('NEXT_PUBLIC_APP_NAME'),
} as const;
Client-exposed variables (NEXT_PUBLIC_*) are embedded at build time:
// ❌ This won't work - value is set at build time
const apiUrl = process.env.NEXT_PUBLIC_API_URL; // Set during `next build`
// ✅ Use different variables for different environments
// .env.development
NEXT_PUBLIC_API_URL=http://localhost:3001
// .env.production
NEXT_PUBLIC_API_URL=https://api.production.com
Server variables are available at runtime:
// ✅ Server variables can change at runtime
export async function GET() {
const dbUrl = process.env.DATABASE_URL; // Available at runtime
return NextResponse.json({ url: dbUrl });
}
// lib/config.ts
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
export const config = {
apiUrl: isDevelopment
? 'http://localhost:3001'
: process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com',
enableDebug: isDevelopment,
logLevel: isProduction ? 'error' : 'debug',
};
# .env.local
NEXT_PUBLIC_ENV=development
const env = process.env.NEXT_PUBLIC_ENV || 'development';
export const config = {
apiUrl: env === 'production'
? 'https://api.production.com'
: 'http://localhost:3001',
};
Set in Vercel dashboard:
.env.development values.env.production valuesVercel provides these automatically:
VERCEL_URL - Deployment URLVERCEL_ENV - development, preview, or productionNODE_ENV - production in production buildsexport async function GET() {
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000';
return NextResponse.json({ baseUrl });
}
.env.local to .gitignoreNEXT_PUBLIC_// lib/api-config.ts
export const apiConfig = {
baseUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
timeout: parseInt(process.env.API_TIMEOUT || '5000', 10),
retries: parseInt(process.env.API_RETRIES || '3', 10),
} as const;
// lib/db-config.ts
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
});
export default pool;
// lib/features.ts
export const features = {
enableAnalytics: process.env.NEXT_PUBLIC_ENABLE_ANALYTICS === 'true',
enableBetaFeatures: process.env.NEXT_PUBLIC_ENABLE_BETA === 'true',
maintenanceMode: process.env.MAINTENANCE_MODE === 'true',
} as const;
NEXT_PUBLIC_.env.local should be in project root.env.local is ignoredAdd types to env.d.ts:
declare namespace NodeJS {
interface ProcessEnv {
YOUR_VAR: string;
}
}