Return_to_vault
[CONSTRUCT: 2026-01-22]
Zod Environment Validation
TypeScriptZodDevOps
Zod Environment Validation
Type-safe env var validation with separate schemas for server and client. Server-side vars get the full schema. Client-side only validates NEXT_PUBLIC_* vars (because that's all the browser can see). If validation fails in CI, it warns instead of crashing so your builds don't break over missing secrets in preview environments.
When to Use
- Any Next.js app where you're tired of
process.env.THING!assertions everywhere - Catching missing or malformed env vars at startup instead of at runtime when a user hits the broken path
- CI pipelines where you need graceful degradation when not all secrets are available
The Code
import { z } from "zod";
const serverSchema = z.object({
DATABASE_URL: z.string().url(),
SESSION_SECRET: z.string().min(32),
});
const clientSchema = z.object({
NEXT_PUBLIC_SITE_URL: z.string().url(),
});
const envSchema = clientSchema.merge(serverSchema);
const isServer = typeof window === "undefined";
const parsed = isServer
? envSchema.safeParse(process.env)
: clientSchema.safeParse({
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
});
let env = process.env as unknown as z.infer<typeof envSchema>;
if (parsed.success) {
env = parsed.data as unknown as z.infer<typeof envSchema>;
} else {
const isCI = !!process.env.SKIP_ENV_VALIDATION || !!process.env.CI;
if (isCI) {
console.warn("Skipping env validation in CI/Build.");
} else {
console.error("Invalid environment variables:",
JSON.stringify(parsed.error.format(), null, 4));
}
}
export { env };
Notes
The SKIP_ENV_VALIDATION escape hatch is intentional. Some CI environments (like Vercel preview deploys) don't have all secrets available. The fallback lets the build succeed while still catching real issues in production.