Skip to main content
apps/web reads environment variables through Vite, validates them with Zod at startup, and exposes a single typed config object. If a required variable is missing or malformed, the app fails fast instead of breaking at runtime.

The VITE_ prefix

Vite only exposes variables that start with VITE_ to client code, and it does so on import.meta.env. Anything without the prefix stays server-side and is invisible to the browser.
Every VITE_-prefixed variable is bundled into the client and visible to anyone who opens devtools. Never put secrets — API keys with write scope, database URLs, signing keys — behind a VITE_ prefix.

One typed config object

Don’t read import.meta.env directly in components. Read from the validated config object in src/config/index.ts, which is the single source of truth:
src/config/index.ts
The raw VITE_* names map onto clean, prefix-free config keys. Consume them anywhere:
validateConfig (in src/utils/config.util.ts) parses the env with the schema and throws a readable error if anything is wrong:
src/utils/config.util.ts

TypeScript autocomplete on import.meta.env

src/env.d.ts augments Vite’s types so each VITE_* variable is typed and autocompletable:
src/env.d.ts

Per-environment .env files

Vite loads the right file from the mode it runs in. vite dev uses development, vite build uses production, and you can target any mode explicitly with --mode:
A development file looks like this:
.env.development

Adding a new variable

1

Add it to the .env files

Prefix client-side variables with VITE_ so Vite exposes them. Add the variable to each environment file that needs it.
.env.development
2

Extend the schema in src/config/index.ts

Add the variable to the Zod schema so it’s validated. Use .optional() if it isn’t required in every environment.
src/config/index.ts
3

Map it in processEnv

Wire the raw VITE_ name to the config key in the same file.
src/config/index.ts
4

Read it through config

Import config and use the typed key. config.STRIPE_PUBLIC_KEY is now strongly typed everywhere.
Keep src/env.d.ts in sync too — adding the new VITE_* key there gives you autocomplete and type-safety on import.meta.env.