T
TurbocampDocs
Getting started

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 configuration

Core 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 32

The 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:

  1. Define the schema in the appropriate keys.ts file:
// 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,
    },
  });
  1. 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
  ],
});
  1. 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.sh

This script will:

  • Generate a secure Better Auth secret
  • Create all necessary .env files 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 script

Validation & 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_SECRET

Runtime 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" | undefined

Custom 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

  1. Never commit .env files - They're in .gitignore for a reason
  2. Use different secrets per environment - Dev, staging, and production should have unique values
  3. Rotate secrets regularly - Especially after team member changes
  4. Use secret managers in production - AWS Secrets Manager, Vercel, etc.
  5. 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:

  1. Defined in the appropriate keys.ts file
  2. Added to the runtimeEnv object
  3. Present in your .env file
  4. Properly imported in the app's env.ts

TypeScript doesn't recognize env variable

  1. Check that you're importing from @/env, not using process.env
  2. Ensure the variable is defined in the schema
  3. Restart your TypeScript server (VS Code: Cmd+Shift+P → "Restart TS Server")

Variable works locally but not in production

  1. Verify the variable is set in your deployment platform
  2. Check for typos in variable names
  3. Ensure build-time variables are available during build
  4. 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.