Configuration

Project identity, environment variables, and the validation that keeps production safe.

Project config

src/project.config.ts is your project's identity in one file:

export const projectConfig = {
  id: 'my-app',
  label: 'my-app',
  brandAccent: {
    hex: '#10b981',
    lightOklch: 'oklch(0.596 0.145 163.225)',
    darkOklch: 'oklch(0.765 0.177 163.223)',
  },
};

The brand accent feeds the UI theme variables, and the same config drives the dev server banner. genesis-start stamps this file when scaffolding.

App-level constants come from src/config/config.ts, which reads the client environment: APP_NAME and APP_URL resolve from VITE_APP_NAME and VITE_APP_URL.

Environment variables

Environment schemas live in src/env/schemas/, split by area: general, auth, database, and email. They merge into one validated schema in src/env/schema.ts, and the app refuses to boot if validation fails.

Two kinds of variables:

  • Server vars (DATABASE_URL, BETTER_AUTH_SECRET, EMAIL_PROVIDER, ENABLE_*): read at runtime from process.env, accessed in code via serverEnv
  • Client vars (VITE_* prefixed): inlined into the browser bundle at build time, accessed via clientEnv

Never import serverEnv in client code.

Production safety

The schema enforces stricter rules when NODE_ENV=production:

  • BETTER_AUTH_SECRET must be at least 32 characters
  • BETTER_AUTH_URL must not be localhost
  • EMAIL_PROVIDER=preview is rejected (use a real provider, or none as an explicit opt-out)

Module flags validate their prerequisites: ENABLE_JOBS requires REDIS_URL, ENABLE_AI_CHAT requires OPENAI_API_KEY, and ENABLE_POLAR requires an access token matching POLAR_ENV.

Keeping .env.example honest

bun run env:check

This verifies .env.example matches the schema keys exactly, so the example file never drifts from what the app actually reads. It runs as part of smoke:prep.