Environment Setup
Configure environment variables for development and production.
Environment variables are managed with t3-env for type-safe validation and runtime safety.
Overview
Environment variables are validated at module load using t3-env (built on Zod), ensuring type safety, clear error messages, and preventing runtime errors from missing configuration.
Development Setup
Create .env.local files in each app:
# apps/api/.env.local
DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
PORT=3001
# apps/web/.env.local
NEXT_PUBLIC_API_URL=http://localhost:3001Validation Pattern
All apps use t3-env for environment variable validation:
- Next.js apps (
apps/web,apps/docs): Use@t3-oss/env-nextjs - Node/Fastify apps (
apps/api): Use@t3-oss/env-core
Next.js Apps
Each Next.js app has lib/env.ts with server/client separation:
// apps/web/lib/env.ts
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
export const env = createEnv({
server: {
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
SENTRY_ORG: z.string().min(1).optional(),
},
client: {
NEXT_PUBLIC_API_URL: z.string().min(1),
NEXT_PUBLIC_SENTRY_DSN: z.string().min(1).optional(),
},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
SENTRY_ORG: process.env.SENTRY_ORG,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
},
emptyStringAsUndefined: true,
})Key points:
serverschema contains server-only variables (noNEXT_PUBLIC_prefix)clientschema contains onlyNEXT_PUBLIC_*variablesruntimeEnvmust explicitly list ALL keys (server + client) for Next.js bundling- Consumption:
env.NEXT_PUBLIC_API_URL(not camelCase)
Server Apps (Fastify/Node)
Server apps use @t3-oss/env-core:
// apps/api/src/lib/env.ts
import { createEnv } from '@t3-oss/env-core'
import { z } from 'zod'
export const env = createEnv({
server: {
PORT: z.coerce.number().int().positive().default(3000),
HOST: z.string().default('0.0.0.0'),
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
DATABASE_URL: z.string().min(1).optional(),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
})Key points:
- Schema keys match actual environment variable names (UPPERCASE)
runtimeEnv: process.envpasses through all env vars- Consumption:
env.DATABASE_URL,env.PORT(not camelCase)
Consumption Pattern
Always import env from lib/env.ts:
// ✅ Correct
import { env } from '@/lib/env'
const apiUrl = env.NEXT_PUBLIC_API_URL
// ❌ Wrong - never use process.env directly
const apiUrl = process.env.NEXT_PUBLIC_API_URLIn Next.js Client Components:
- Only access
env.NEXT_PUBLIC_*variables - Accessing server-only vars will throw at runtime (t3-env protection)
Required Variables
API (apps/api)
DATABASE_URL- PostgreSQL connection string (optional)PORT- Server port (default: 3000)HOST- Server host (default: 0.0.0.0)
Web (apps/web)
NEXT_PUBLIC_API_URL- API base URL (required)
Docs (apps/docs)
NEXT_PUBLIC_SITE_URL- Site URL for metadata (default: https://docs.basilic.dev)
Important Notes
NEXT_PUBLIC_* Variables
⚠️ Warning: NEXT_PUBLIC_* variables are:
- Public - Exposed to the browser (never put secrets here)
- Build-time inlined - Replaced at build time, not runtime
- Cannot be changed without rebuilding
runtimeEnv Requirement (Next.js)
In Next.js apps, runtimeEnv must explicitly list every key from both server and client schemas. This is required for Next.js bundling to correctly identify which variables are used.
Type Safety
t3-env provides:
- Runtime validation (fails fast if invalid)
- TypeScript type inference from Zod schemas
- Protection against accessing server vars in client code (Next.js)
Production
Set environment variables in your deployment platform:
- Vercel: Project Settings → Environment Variables
- Google Cloud: Cloud Run → Environment Variables
- AWS: ECS Task Definition → Environment Variables
Related Documentation
- Environment Rules - Environment variable patterns
- Portability Strategy - Deployment options