Project identity, environment variables, and the validation that keeps production safe.
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 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:
DATABASE_URL, BETTER_AUTH_SECRET, EMAIL_PROVIDER, ENABLE_*): read at runtime from process.env, accessed in code via serverEnvVITE_* prefixed): inlined into the browser bundle at build time, accessed via clientEnvNever import serverEnv in client code.
The schema enforces stricter rules when NODE_ENV=production:
BETTER_AUTH_SECRET must be at least 32 charactersBETTER_AUTH_URL must not be localhostEMAIL_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.
bun run env:checkThis 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.