Careeroza
Environment Variables
Files and Priority
| File | Loaded when | Commit to git? |
|---|---|---|
.env | Always (defaults) | Yes, no secrets |
.env.development / .env.production | next dev / next build+start | Yes, no secrets |
.env.local | Always except tests; overrides the above | No |
| Real environment variables | Always; highest priority | — |
NEXT_PUBLIC_ Is Inlined at Build Time
NEXT_PUBLIC_* values are copied into the JavaScript bundle during next build. Changing them later on the server has no effect until you rebuild. Server-only variables are read at runtime.
Example
What the bundler does to client code, in miniature:
const buildEnv: Record<string, string> = {
NEXT_PUBLIC_SITE_URL: 'https://shopease.in',
NEXT_PUBLIC_RAZORPAY_KEY_ID: 'rzp_live_abc123',
DATABASE_URL: 'postgresql://user:secret@db:5432/shop',
};
function inlinePublicEnv(source: string): string {
return source.replace(/process\.env\.(\w+)/g, (match, name: string) =>
name.startsWith('NEXT_PUBLIC_') ? JSON.stringify(buildEnv[name] ?? '') : 'undefined');
}
const clientCode = [
"const site = process.env.NEXT_PUBLIC_SITE_URL;",
"const key = process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID;",
"const db = process.env.DATABASE_URL;",
].join('\n');
console.log(inlinePublicEnv(clientCode));Output
const site = "https://shopease.in"; const key = "rzp_live_abc123"; const db = undefined;
Note: Never prefix a secret with NEXT_PUBLIC_. Anyone can read it in the browser's JS files.
Validate at Startup
// lib/env.ts
import 'server-only';
import { z } from 'zod';
export const env = z
.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
RAZORPAY_KEY_SECRET: z.string().min(10),
})
.parse(process.env); // crashes early with a clear message if something is missing