Documentation
Documentation
Introduction

Getting Started

Getting StartedInstallationQuick StartProject Structure

Architecture

Architecture OverviewTech StacktRPC MiddlewareDesign Principles

Patterns

Code Patterns & ConventionsFeature ModulesError HandlingType Safety

Database

DatabaseSchema DefinitionDatabase OperationsMigrationsCaching

API

tRPCProceduresRouterstRPC Proxy Setup
APIsOpenAPIREST Endpoints

Auth & Access

AuthenticationConfigurationOAuth ProvidersRolesSession Management
AuthorizationUser RolesPermissions

Routing & i18n

RoutingDeclarative RoutingNavigation
InternationalizationTranslationsLocale Routing

Components & UI

ComponentsButtonsFormsNavigationDialogs
StylesTailwind CSSThemingTypography

Storage

StorageConfigurationUsageBuckets

Configuration

ConfigurationEnvironment VariablesFeature Flags

Templates

Template GuidesCreate New FeatureCreate New PageCreate Database TableCreate tRPC RouterAdd Translations

Development

DevelopmentCommandsAI AgentsBest Practices

Environment Variables

Configure environment variables

Environment variables are validated using Zod schemas with separate files for server and client variables.

Structure

src/env/
├── env-server.ts  # Server-side environment variables
└── env-client.ts  # Client-side environment variables (NEXT_PUBLIC_*)

Server-Side Variables

Defined in src/env/env-server.ts:

import { z } from "zod";

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(1),
  STRIPE_SECRET_KEY: z.string().min(1),
  // Add more server-side variables...
});

export const env = envSchema.parse(process.env);

Usage

import { env } from "@/env/env-server";

const dbUrl = env.DATABASE_URL;
const stripeKey = env.STRIPE_SECRET_KEY;

Server Only

These variables are only available on the server. Do not try to access them in client components.

Client-Side Variables

Defined in src/env/env-client.ts:

import { z } from "zod";

const envSchema = z.object({
  NEXT_PUBLIC_API_URL: z.string().url(),
  NEXT_PUBLIC_APP_NAME: z.string().min(1),
  NEXT_PUBLIC_ENABLE_ANALYTICS: z.coerce.boolean(),
  // Add more client-side variables...
});

export const env = envSchema.parse({
  NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
  NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
  NEXT_PUBLIC_ENABLE_ANALYTICS: process.env.NEXT_PUBLIC_ENABLE_ANALYTICS,
});

Usage

"use client";

import { env } from "@/env/env-client";

const apiUrl = env.NEXT_PUBLIC_API_URL;
const appName = env.NEXT_PUBLIC_APP_NAME;

Client variables must be prefixed with NEXT_PUBLIC_ to be accessible in the browser.

Environment Files

Create a .env.local file in the project root:

# Database
DATABASE_URL="postgresql://user:password@localhost:5432/db"

# Authentication
NEXTAUTH_SECRET="your-secret-key"
BETTER_AUTH_SECRET="your-better-auth-secret"

# Stripe
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."

# App Config
NEXT_PUBLIC_API_URL="http://localhost:3000"
NEXT_PUBLIC_APP_NAME="My App"

File Priority

FilePurposeCommit?
.env.localLocal development overridesNo
.envDefault valuesYes
.env.productionProduction defaultsYes
.env.developmentDevelopment defaultsYes

Validation

Type Safety

Environment variables are fully type-safe:

import { env } from "@/env/env-server";

env.DATABASE_URL;  // ✅ string (validated URL)
env.INVALID_KEY;   // ❌ TypeScript error

Runtime Validation

Invalid environment variables cause the app to fail at startup:

Error: Invalid environment variables:
  - DATABASE_URL: Invalid url
  - STRIPE_SECRET_KEY: Required

Adding New Variables

Step 1: Choose server or client

Server-side: env-server.ts
Client-side: env-client.ts (must prefix with NEXT_PUBLIC_)

Step 2: Add to schema

const envSchema = z.object({
  // ... existing variables
  NEW_VARIABLE: z.string().min(1),
});

Step 3: Add to .env.local

NEW_VARIABLE="value"

Step 4: Use in code

import { env } from "@/env/env-server";
const value = env.NEW_VARIABLE;

Common Patterns

URLs

DATABASE_URL: z.string().url()
NEXT_PUBLIC_API_URL: z.string().url()

Booleans

ENABLE_FEATURE: z.coerce.boolean()
NEXT_PUBLIC_ANALYTICS: z.coerce.boolean()

Numbers

PORT: z.coerce.number()
MAX_UPLOAD_SIZE: z.coerce.number()

Enums

NODE_ENV: z.enum(["development", "production", "test"])
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"])

Optional with Defaults

PORT: z.coerce.number().default(3000)
ENABLE_CACHE: z.coerce.boolean().default(true)

Vercel Deployment

Add environment variables in the Vercel dashboard:

  1. Go to Project Settings → Environment Variables
  2. Add each variable for Production, Preview, and Development
  3. Redeploy to apply changes

Never commit .env.local to version control. Use .env.example to document required variables.

Example .env.example

Create an .env.example file for documentation:

# Database
DATABASE_URL="postgresql://user:password@localhost:5432/db"

# Authentication
NEXTAUTH_SECRET="generate-a-secret-key"
BETTER_AUTH_SECRET="generate-another-secret-key"

# Stripe
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."

# Optional: Analytics
# NEXT_PUBLIC_GA_ID=""

Best Practices

  1. Validate everything - Always add Zod validation for new environment variables
  2. Use type coercion - Use z.coerce for numbers and booleans
  3. Provide defaults - Use .default() for optional variables
  4. Document in .env.example - Keep the example file up to date
  5. Never commit secrets - Keep .env.local in .gitignore

On this page

Structure
Server-Side Variables
Usage
Client-Side Variables
Usage
Environment Files
File Priority
Validation
Type Safety
Runtime Validation
Adding New Variables
Step 1: Choose server or client
Step 2: Add to schema
Step 3: Add to .env.local
Step 4: Use in code
Common Patterns
URLs
Booleans
Numbers
Enums
Optional with Defaults
Vercel Deployment
Example .env.example
Best Practices