Local Setup & Env Vars
Managing environment variables and local development configuration
Zeno uses environment variables for configuration across different environments.
Environment Files
.env # Default values (committed)
.env.local # Local overrides (not committed)
.env.development # Development-specific
.env.production # Production-specific
.env.test # Test-specificLoading Order
Environment variables are loaded in this order (later overrides earlier):
.env.env.local.env.[NODE_ENV].env.[NODE_ENV].local
Next.js Variables
For Next.js apps, prefix public variables with NEXT_PUBLIC_:
# Server-only (secure)
DATABASE_URL=postgres://...
API_SECRET=secret123
# Browser-accessible
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My AppTypeScript Support
Create a typed env file for autocomplete:
export const env = {
DATABASE_URL: process.env.DATABASE_URL!,
API_SECRET: process.env.API_SECRET!,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL!,
}Vitest Environment
Tests use .env.test and NODE_ENV=test. The shared config from @zeno-lib/test loads env vars automatically:
export { defaultConfig as default } from "@zeno-lib/test/configs"Turborepo
Pass environment variables to Turborepo tasks:
{
"globalEnv": ["NODE_ENV"],
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", ".env*"]
},
"e2e": {
"passThroughEnv": ["PLAYWRIGHT_*"]
}
}
}Security
- Never commit secrets — Use
.env.localfor sensitive values - Use
.gitignore— Ensure.env*.localis ignored - Validate at startup — Check required vars exist
- Rotate credentials — Update secrets regularly
Example validation:
function validateEnv() {
const required = ["DATABASE_URL", "API_SECRET"]
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required env var: ${key}`)
}
}
}