Environment Variables
Complete guide to configuring type-safe environment variables for all Turbocamp applications
Overview
Turbocamp uses a type-safe environment variable system powered by @t3-oss/env-nextjs. This ensures all environment variables are validated at build time and provides full TypeScript support throughout your application.
Type Safety First: All environment variables are validated using Zod schemas and accessed through typed env objects. Never use process.env directly!
Type-Safe Environment System
How It Works
Each package and app defines its environment variables in a keys.ts file:
// packages/auth/keys.ts
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const keys = () =>
createEnv({
server: {
BETTER_AUTH_SECRET: z.string().min(32),
BETTER_AUTH_URL: z.string().url().optional(),
},
client: {
NEXT_PUBLIC_AUTH_API_URL: z.string().url(),
},
runtimeEnv: {
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
NEXT_PUBLIC_AUTH_API_URL: process.env.NEXT_PUBLIC_AUTH_API_URL,
},
});Using Environment Variables
Always import and use the typed env object:
// ✅ CORRECT - Type-safe access
import { env } from '@/env';
if (env.VERCEL) {
// This is type-safe and validated
}
// ❌ WRONG - Never do this
if (process.env.VERCEL) {
// This bypasses type safety
}Environment Structure
Each application in the monorepo has its own environment file:
apps/
├── api/.env.local # API server configuration
├── dashboard/.env.local # Dashboard app configuration
├── web/.env.local # Web app configuration
└── docs/.env.local # Documentation site configuration
packages/
├── db/.env # Database configuration
└── i18n/.env.local # Internationalization configurationCore Environment Variables
Authentication (packages/auth)
# Required: Better Auth secret (min 32 characters)
BETTER_AUTH_SECRET="your-32-character-secret-key-here"
# Optional: Better Auth URL
BETTER_AUTH_URL="http://localhost:3002"
# API URL for auth endpoints
AUTH_API_URL="http://localhost:3002"
# Optional: Additional trusted origins for CORS
ADDITIONAL_TRUSTED_ORIGINS="http://localhost:3003,http://localhost:3004"
# Client-side auth URLs
NEXT_PUBLIC_BETTER_AUTH_URL="http://localhost:3002"
NEXT_PUBLIC_AUTH_API_URL="http://localhost:3002"Generate a secure secret:
openssl rand -base64 32The BETTER_AUTH_SECRET must be identical across all applications for session sharing to work properly.
Database (packages/db)
# Required: PostgreSQL connection URL
DATABASE_URL="postgresql://username:password@localhost:5432/turbocamp?schema=public"# Local PostgreSQL
DATABASE_URL="postgresql://postgres@localhost:5432/turbocamp"
# With password
DATABASE_URL="postgresql://postgres:password@localhost:5432/turbocamp"# Neon database URL (get from Neon dashboard)
DATABASE_URL="postgresql://username:password@ep-xxx.region.aws.neon.tech/turbocamp?sslmode=require"# Supabase database URL (get from Supabase dashboard)
DATABASE_URL="postgresql://username:password@db.project.supabase.co:5432/postgres"Application URLs (tooling/next-config)
# Required: Application URLs
NEXT_PUBLIC_DASHBOARD_URL="http://localhost:3001"
NEXT_PUBLIC_WEB_URL="http://localhost:3000"
# Optional: API and docs URLs
NEXT_PUBLIC_API_URL="http://localhost:3002"
NEXT_PUBLIC_DOCS_URL="http://localhost:3004"
# Build/deployment variables
ANALYZE="true" # Enable bundle analysis
VERCEL="1" # Set by Vercel automatically
VERCEL_PROJECT_PRODUCTION_URL="your-app.vercel.app"
NEXT_RUNTIME="nodejs" # or "edge"Service Integrations
Email Service (packages/email)
# Required: Resend configuration
RESEND_FROM="noreply@yourdomain.com"
RESEND_TOKEN="re_xxxxxxxxxxxxxxxx"Payments (packages/payments)
# Required: Stripe configuration
STRIPE_SECRET_KEY="sk_test_xxxxxxxxxxxxxxxx"
# Optional: Webhook secret for Stripe events
STRIPE_WEBHOOK_SECRET="whsec_xxxxxxxxxxxxxxxx"Analytics (packages/analytics)
# Required: PostHog configuration
NEXT_PUBLIC_POSTHOG_KEY="phc_xxxxxxxxxxxxxxxx"
NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com"
# Optional: Google Analytics
NEXT_PUBLIC_GA_MEASUREMENT_ID="G-XXXXXXXXXX"Error Tracking (packages/logging)
# Optional: Sentry configuration
SENTRY_ORG="your-org"
SENTRY_PROJECT="your-project"
NEXT_PUBLIC_SENTRY_DSN="https://xxxxxxxx@sentry.io/xxxxxxxx"Rate Limiting (packages/security)
# Optional: Upstash Redis for rate limiting
UPSTASH_REDIS_REST_URL="https://xxxxxxxx.upstash.io"
UPSTASH_REDIS_REST_TOKEN="xxxxxxxxxxxxxxxx"Feature Flags (packages/feature-flags)
# Optional: Feature flags secret
FLAGS_SECRET="your-feature-flags-secret"AI Integration (packages/ai)
# Optional: OpenAI API key
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxx"File Storage (packages/storage)
# Optional: Vercel Blob storage
BLOB_READ_WRITE_TOKEN="vercel_blob_xxxxxxxx"Internationalization (packages/i18n)
# Optional: Languine project ID for translations
LANGUINE_PROJECT_ID="your-project-id"Application Examples
API App (.env.local)
# Database
DATABASE_URL="postgresql://postgres@localhost:5432/turbocamp"
# Authentication
BETTER_AUTH_SECRET="your-32-character-secret"
BETTER_AUTH_URL="http://localhost:3002"
# Optional services
RESEND_FROM="noreply@localhost"
RESEND_TOKEN="re_test_xxxxxxxx"
STRIPE_SECRET_KEY="sk_test_xxxxxxxx"
UPSTASH_REDIS_REST_URL="https://xxxxxxxx.upstash.io"
UPSTASH_REDIS_REST_TOKEN="xxxxxxxxxxxxxxxx"Dashboard App (.env.local)
# Authentication
BETTER_AUTH_SECRET="your-32-character-secret"
AUTH_API_URL="http://localhost:3002"
NEXT_PUBLIC_AUTH_API_URL="http://localhost:3002"
# Application URLs
NEXT_PUBLIC_DASHBOARD_URL="http://localhost:3001"
NEXT_PUBLIC_WEB_URL="http://localhost:3000"
# Analytics
NEXT_PUBLIC_POSTHOG_KEY="phc_xxxxxxxx"
NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com"Web App (.env.local)
# Authentication
AUTH_API_URL="http://localhost:3002"
NEXT_PUBLIC_AUTH_API_URL="http://localhost:3002"
# Application URLs
NEXT_PUBLIC_WEB_URL="http://localhost:3000"
NEXT_PUBLIC_DASHBOARD_URL="http://localhost:3001"
# Analytics
NEXT_PUBLIC_POSTHOG_KEY="phc_xxxxxxxx"
NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com"Adding New Environment Variables
To add a new environment variable:
- Define the schema in the appropriate
keys.tsfile:
// packages/your-package/keys.ts
export const keys = () =>
createEnv({
server: {
MY_NEW_VAR: z.string().min(1),
},
runtimeEnv: {
MY_NEW_VAR: process.env.MY_NEW_VAR,
},
});- Import the keys in the app's
env.ts:
// apps/your-app/env.ts
import { keys as yourPackageKeys } from '@packages/your-package/keys';
export const env = createEnv({
extends: [
yourPackageKeys(),
// ... other keys
],
});- Use the typed env in your code:
import { env } from '@/env';
console.log(env.MY_NEW_VAR); // Type-safe!Quick Setup Script
We've created a convenient setup script that automatically creates all required environment files with proper configuration:
# Run the setup script (Recommended)
./scripts/setup-env.shThis script will:
- Generate a secure Better Auth secret
- Create all necessary
.envfiles for each app and package - Set up database connections and authentication URLs
- Include commented examples for optional services
Manual Setup (Alternative)
If you prefer to set up environment files manually, you can copy the example files and configure them:
# Copy example files (if they exist)
cp apps/api/.env.example apps/api/.env.local
cp apps/dashboard/.env.example apps/dashboard/.env.local
cp apps/web/.env.example apps/web/.env.local
cp packages/db/.env.example packages/db/.env
# Or create them manually following the examples in the automated scriptValidation & Type Safety
Build-Time Validation
The @t3-oss/env package validates all environment variables at build time:
# Missing required variables will fail the build
Error: Missing required environment variable: BETTER_AUTH_SECRETRuntime Type Safety
All environment variables are fully typed:
env.BETTER_AUTH_SECRET // string
env.NEXT_PUBLIC_POSTHOG_KEY // string
env.ANALYZE // string | undefined
env.NEXT_RUNTIME // "nodejs" | "edge" | undefinedCustom Validation
Add custom validation rules:
// Validate email format
RESEND_FROM: z.string().email(),
// Validate URL format
BETTER_AUTH_URL: z.string().url(),
// Validate specific prefixes
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
// Validate minimum length
BETTER_AUTH_SECRET: z.string().min(32),Security Best Practices
Security Rules
- Never commit
.envfiles - They're in.gitignorefor a reason - Use different secrets per environment - Dev, staging, and production should have unique values
- Rotate secrets regularly - Especially after team member changes
- Use secret managers in production - AWS Secrets Manager, Vercel, etc.
- Limit access - Only necessary team members should have production secrets
Client vs Server Variables
- Server variables: Can contain secrets, only available server-side
- Client variables: Must start with
NEXT_PUBLIC_, visible in browser
// Server-only (secret)
BETTER_AUTH_SECRET="secret-key"
// Client-side (public)
NEXT_PUBLIC_API_URL="https://api.example.com"Troubleshooting
Common Issues
Build fails with missing env var
Ensure the variable is:
- Defined in the appropriate
keys.tsfile - Added to the
runtimeEnvobject - Present in your
.envfile - Properly imported in the app's
env.ts
TypeScript doesn't recognize env variable
- Check that you're importing from
@/env, not usingprocess.env - Ensure the variable is defined in the schema
- Restart your TypeScript server (VS Code: Cmd+Shift+P → "Restart TS Server")
Variable works locally but not in production
- Verify the variable is set in your deployment platform
- Check for typos in variable names
- Ensure build-time variables are available during build
- Remember: Client variables need
NEXT_PUBLIC_prefix
Debug Environment Variables
// Debug all loaded environment variables (dev only!)
if (process.env.NODE_ENV === 'development') {
console.log('Loaded env:', env);
}Need help? Check the specific package's keys.ts file to see all available environment variables and their validation rules.