git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/infra-config-setup-env" ~/.claude/skills/neversight-learn-skills-dev-infra-config-setup-env && rm -rf "$T"
data/skills-md/agents-inc/skills/infra-config-setup-env/SKILL.mdEnvironment Management
Quick Guide: Per-app .env files. Framework-specific prefixes (
for Next.js,NEXT_PUBLIC_*for Vite). Zod validation at startup. Maintain .env.example templates. Never commit secrets (.gitignore). Environment-based feature flags.VITE_*
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST validate ALL environment variables with Zod at application startup)
(You MUST use framework-specific prefixes for client-side variables -
for Next.js, NEXT_PUBLIC_*
for Vite)VITE_*
(You MUST maintain .env.example templates with ALL required variables documented)
(You MUST never commit secrets to version control - use .env.local and CI secrets)
(You MUST use per-app .env files - NOT root-level .env files)
</critical_requirements>
Auto-detection: Environment variables, .env files, Zod validation, t3-env, @t3-oss/env, secrets management,
NEXT_PUBLIC_ prefix, VITE_ prefix, feature flags, z.stringbool
When to use:
- Setting up Zod validation for type-safe environment variables at startup
- Managing per-app .env files with framework-specific prefixes
- Securing secrets (never commit, use .env.local and CI secrets)
- Implementing environment-based feature flags
When NOT to use:
- Runtime configuration changes (use an external feature flag service)
- User-specific settings (use database or user preferences)
- Frequently changing values (use configuration API or database)
- Complex A/B testing with gradual rollouts (use a dedicated feature flag service)
Key patterns covered:
- Per-app .env files (not root-level, prevents conflicts)
- Zod validation at startup for type safety and early failure
- T3 Env pattern for Next.js/Vite projects (recommended)
- Framework-specific prefixes (
for client,NEXT_PUBLIC_*
for Vite client)VITE_* - .env.example templates for documentation and onboarding
Detailed Resources:
- For code examples, see examples/ folder:
- examples/core.md - Essential patterns (per-app .env, Zod validation)
- examples/t3-env.md - T3 Env pattern for Next.js/Vite (recommended)
- examples/naming-and-templates.md - Framework prefixes, .env.example
- examples/security-and-secrets.md - Secret management
- examples/feature-flags-and-config.md - Feature flags, centralized config
- For decision frameworks and anti-patterns, see reference.md
<philosophy>
Philosophy
Environment management follows the principle that configuration is code -- it should be validated, typed, and versioned. The system uses per-app .env files with framework-specific prefixes, Zod validation at startup, and strict security practices to prevent secret exposure.
</philosophy><patterns>
Core Patterns
Pattern 1: Per-App Environment Files
Each app/package has its own
.env file to prevent conflicts and clarify ownership.
File Structure
apps/ ├── client-next/ │ ├── .env # Local development (NEXT_PUBLIC_API_URL) │ └── .env.production # Production overrides ├── client-react/ │ ├── .env # Local development │ └── .env.production # Production overrides └── server/ ├── .env # Local server config ├── .env.example # Template for new developers └── .env.local.example # Local overrides template packages/ ├── api/ │ └── .env # API package config └── api-mocks/ └── .env # Mock server config
File Types and Purpose
- Default development values (committed for apps, gitignored for sensitive packages).env
- Documentation template (committed, shows all required variables).env.example
- Local developer overrides (gitignored, takes precedence over.env.local
).env
- Production configuration (committed or in CI secrets).env.production
- Local override template (committed).env.local.example
Loading Order and Precedence
Next.js loading order (highest to lowest priority):
(already set in environment)process.env
(e.g.,.env.$(NODE_ENV).local
).env.production.local
(not loaded when.env.local
)NODE_ENV=test
(e.g.,.env.$(NODE_ENV)
).env.production.env
Vite loading order:
(e.g.,.env.[mode].local
).env.production.local
(e.g.,.env.[mode]
).env.production.env.local.env
Exception: Shared variables can go in your build tool's env configuration for cache invalidation
See examples/core.md for complete code examples.
Pattern 2: Type-Safe Environment Variables with Zod
Validate environment variables at application startup using Zod schemas. Define a schema, parse at startup, export a typed
env object.
// lib/env.ts const envSchema = z.object({ VITE_API_URL: z.string().url(), VITE_API_TIMEOUT: z.coerce.number().default(DEFAULT_API_TIMEOUT_MS), VITE_ENABLE_ANALYTICS: z.stringbool().default(false), // Zod 4+ (NOT z.coerce.boolean()) }); export const env = envSchema.parse(import.meta.env);
Key gotchas:
convertsz.coerce.boolean()
to"false"
(string is truthy) - always usetrue
insteadz.stringbool()- Use
(noterror.issues
) for Zod 4 error handlingerror.errors
Note: For Next.js/Vite projects, consider T3 Env (
or@t3-oss/env-nextjs) for client/server variable separation and build-time validation. See examples/t3-env.md.@t3-oss/env-core
See examples/core.md for complete good/bad comparisons.
Pattern 3: Framework-Specific Naming Conventions
Use framework-specific prefixes for client-side variables and SCREAMING_SNAKE_CASE for all environment variables.
Mandatory Conventions
- SCREAMING_SNAKE_CASE - All environment variables use uppercase with underscores
- Descriptive names - Variable names clearly indicate purpose
- Framework prefixes - Use
(Next.js) orNEXT_PUBLIC_*
(Vite) for client-side variablesVITE_*
Framework Prefixes
Next.js:
- Client-side accessible (embedded in bundle) - use for API URLs, public keys, feature flagsNEXT_PUBLIC_*- No prefix - Server-side only (database URLs, secret keys, API tokens)
Vite:
- Client-side accessible (embedded in bundle) - use for API URLs, public configurationVITE_*- No prefix - Build-time only (not exposed to client)
Node.js/Server:
- Standard environment (NODE_ENV
,development
,production
)test
- Server port numberPORT- No prefix - All variables available server-side
See examples/naming-and-templates.md for complete code examples with good/bad comparisons.
</patterns><integration>
Integration Guide
Core dependencies:
- Zod (v4+): Runtime validation and type inference for environment variables
- T3 Env (
,@t3-oss/env-nextjs
): Recommended wrapper for client/server separation@t3-oss/env-core
Framework support:
- Next.js: Automatic .env file loading with
prefix for client-sideNEXT_PUBLIC_* - Vite: Automatic .env file loading with
prefix for client-sideVITE_*
Monorepo considerations:
- Declare shared env vars in your build tool's env configuration for cache invalidation
- Use per-app .env files even in monorepos to prevent conflicts
Replaces / Conflicts with:
- Hardcoded configuration values (use env vars instead)
- Runtime feature flag services for simple boolean flags (use env vars first, upgrade when needing gradual rollouts)
<decision_framework>
Decision Framework
See reference.md for complete decision frameworks including environment configuration and feature flag decisions.
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Committing secrets to version control (.env files with real credentials)
- Using environment variables directly without Zod validation (causes runtime errors)
- Using
orNEXT_PUBLIC_*
prefix for secrets (embeds in client bundle)VITE_*
Medium Priority Issues:
- Missing .env.example documentation (poor onboarding experience)
- Using production secrets in development (security risk)
- Root-level .env in monorepo (causes conflicts)
Gotchas:
- Next.js/Vite embed prefixed variables at build time, not runtime - requires rebuild to change
- Environment variables are strings - use
for numbers, usez.coerce.number()
for booleans (Zod 4+)z.stringbool() - CRITICAL:
converts "false" toz.coerce.boolean()
(string is truthy) - usetrue
(Zod 4+) insteadz.stringbool() - Empty string env vars are NOT
- use T3 Env'sundefined
optionemptyStringAsUndefined: true - Monorepo build tool caches may NOT be invalidated by env changes unless declared in the tool's env configuration
See reference.md for complete RED FLAGS, anti-patterns, and checklists.
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST validate ALL environment variables with Zod at application startup)
(You MUST use framework-specific prefixes for client-side variables -
for Next.js, NEXT_PUBLIC_*
for Vite)VITE_*
(You MUST maintain .env.example templates with ALL required variables documented)
(You MUST never commit secrets to version control - use .env.local and CI secrets)
(You MUST use per-app .env files - NOT root-level .env files)
Failure to follow these rules will cause runtime errors, security vulnerabilities, and configuration confusion.
</critical_reminders>