This walks you through setting up environment variables properly with type safety and validation. It creates the full .env file structure you need (development, production, example files), then adds Zod schemas to catch missing or invalid variables on startup instead of at runtime. Includes ready to use templates for common services like Stripe and database URLs, organized by category with comments on where to get API keys. The T3 env approach for Next.js is especially clean since it enforces the server/client split at the type level. Good for onboarding new projects or fixing that mess where half your team has different .env files and nobody knows what's actually required.
npx -y skills add onewave-ai/claude-skills --skill env-setup-wizard --agent claude-codeInstalls into .claude/skills of the current project.
When setting up environment configuration:
project/
├── .env # Local development (git-ignored)
├── .env.example # Template (committed to git)
├── .env.local # Local overrides (git-ignored)
├── .env.development # Development defaults
├── .env.production # Production defaults
└── src/
└── lib/
└── env.ts # Type-safe env access
# ===================
# Application
# ===================
NODE_ENV=development
APP_URL=http://localhost:3000
PORT=3000
# ===================
# Database
# ===================
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# ===================
# Authentication
# ===================
# Generate with: openssl rand -base64 32
JWT_SECRET=
NEXTAUTH_SECRET=
NEXTAUTH_URL=http://localhost:3000
# ===================
# Third-party APIs
# ===================
# Get from: https://stripe.com/dashboard
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
# Get from: https://resend.com
RESEND_API_KEY=
# ===================
# Storage
# ===================
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
S3_BUCKET_NAME=
// src/lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
// App
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
APP_URL: z.string().url(),
PORT: z.coerce.number().default(3000),
// Database
DATABASE_URL: z.string().min(1),
// Auth
JWT_SECRET: z.string().min(32),
NEXTAUTH_SECRET: z.string().min(32),
NEXTAUTH_URL: z.string().url(),
// APIs (optional in dev)
STRIPE_SECRET_KEY: z.string().optional(),
RESEND_API_KEY: z.string().optional(),
});
// Validate on import
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('Invalid environment variables:');
console.error(parsed.error.flatten().fieldErrors);
process.exit(1);
}
export const env = parsed.data;
// Type export for use elsewhere
export type Env = z.infer<typeof envSchema>;
// src/lib/env.ts for Next.js
import { z } from 'zod';
// Server-side variables
const serverSchema = z.object({
DATABASE_URL: z.string(),
JWT_SECRET: z.string(),
});
// Client-side variables (must start with NEXT_PUBLIC_)
const clientSchema = z.object({
NEXT_PUBLIC_APP_URL: z.string().url(),
NEXT_PUBLIC_STRIPE_KEY: z.string(),
});
export const serverEnv = serverSchema.parse(process.env);
export const clientEnv = clientSchema.parse({
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NEXT_PUBLIC_STRIPE_KEY: process.env.NEXT_PUBLIC_STRIPE_KEY,
});
// src/env.mjs
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(["development", "test", "production"]),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
});
# Environment files
.env
.env.local
.env.*.local
# Keep example
!.env.example
juliusbrussee/caveman
mattpocock/skills
shadcn/improve
obra/superpowers
forrestchang/andrej-karpathy-skills
vercel-labs/skills