| name | t3-env-validation |
| description | Type-safe environment variable validation with @t3-oss/env-nextjs and Zod. Build-time validation ensures all required env vars are present and correctly typed. |
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash, mcp__context7__resolve-library-id, mcp__context7__get-library-docs |
T3 Env Validation Skill
Type-safe environment variable validation with @t3-oss/env-nextjs and Zod.
Reference Files:
Project Configuration
Environment variables are validated in two files:
| File | Purpose | Variables |
|---|
data/env/server.ts | Server-only vars | CLERK_SECRET_KEY, Firebase, etc. |
data/env/client.ts | Public vars | NEXT_PUBLIC_* variables |
Quick Start
Adding Server Environment Variable
import { z } from "zod";
import { createEnv } from "@t3-oss/env-nextjs";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
API_SECRET: z.string().min(32),
},
experimental__runtimeEnv: process.env,
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
emptyStringAsUndefined: true,
});
Adding Client Environment Variable
import { z } from "zod";
import { createEnv } from "@t3-oss/env-nextjs";
export const env = createEnv({
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
experimental__runtimeEnv: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
emptyStringAsUndefined: true,
});
Usage in Code
import { env } from "~/data/env/server";
const apiSecret = env.API_SECRET;
import { env } from "~/data/env/client";
const apiUrl = env.NEXT_PUBLIC_API_URL;
Key Concepts
Build-Time Validation
T3 Env validates environment variables at build time:
npm run build
❌ Invalid environment variables:
CLERK_SECRET_KEY: Required
FIREBASE_PROJECT_ID: Required
This prevents deploying with missing configuration.
Server vs Client Variables
| Type | Prefix | Accessible In | Bundled |
|---|
| Server | None | Server Components, Actions, API | No |
| Client | NEXT_PUBLIC_ | Everywhere | Yes (in JS bundle) |
Security Rule: Never put secrets in client variables!
Empty String Handling
emptyStringAsUndefined: true;
With this setting:
VAR="" is treated as undefined
- Required vars with empty string will fail validation
- Optional vars with empty string use default
Common Patterns
Required String
API_KEY: z.string();
Optional with Default
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"])
.optional()
.default("info");
Boolean Transform
FEATURE_FLAG: z.enum(["true", "false"])
.optional()
.transform((val) => val === "true");
URL Validation
API_URL: z.string().url();
DATABASE_URL: z.string().url().startsWith("postgresql://");
Number Transform
PORT: z.string().transform((val) => parseInt(val, 10));
TIMEOUT_MS: z.coerce.number().int().positive();
File Locations
| Purpose | Location |
|---|
| Server env | data/env/server.ts |
| Client env | data/env/client.ts |
| Env template | .env.example |
| Local env | .env.local |
Troubleshooting
Build Fails with Missing Env
SKIP_ENV_VALIDATION=true npm run build
Variable Not Available
- Server var → Can only be used in server code
- Client var → Must be in
experimental__runtimeEnv
- Check spelling and
NEXT_PUBLIC_ prefix
Type Errors
import { env } from "~/data/env/server";
import { env } from "~/data/env/client";
Related Skills
server-actions - Using env vars in server actions
firebase-firestore - Firebase env configuration
clerk-auth-proxy - Clerk env configuration