Setting up environment variables (Bun + Vite, zod-validated)
.env layout and zod-validated, split server/client env modules for a TanStack Start project.
Prerequisites
- Bun project using Vite
zodinstalled (bun add zod)
Steps
Add the
.envfiles.env— non-secret config, committed.env.local— secrets, gitignored.env.local.example— committed template for.env.local, no real values
# .env # Non-secret environment configuration, use .env.local for secrets # Prefix with VITE_ to make it available from the clientside# .env.local.example # Copy to .env.local and populate with secretsGitignore the secrets file
# .gitignore .env.localWire into project
src/lib/env.server.ts— server-only vars, parsesprocess.env, never imported from client-reachable code// src/lib/env.server.ts import { z } from "zod"; /** * Server-only environment variables. */ const serverEnvSchema = z.object({}); export default serverEnvSchema.parse(process.env);src/lib/env.ts— client-safe vars, parsesimport.meta.env, only variables prefixedVITE_// src/lib/env.ts import { z } from "zod"; /** * Environment variables that are also readable on the client. * Must be prefixed with `VITE_` to be exposed to the browser by Vite. */ const clientEnvSchema = z.object({}); export default clientEnvSchema.parse(import.meta.env);
Add each variable to both the
.env/.env.localfile and the matching zod schema as it's introduced — a var with no schema field isn't validated and won't get type inference
Verification
- Run the dev server with a required var missing from
.env/.env.localand confirm zod throws at startup instead of the app running withundefined - Import
env.server.tsfrom a client component and confirm the build fails or the secret is absent from the client bundle — it should never end up there - Confirm a
VITE_-prefixed var in.envis readable viaenv.tsin the browser
Gotchas
- Vite only exposes vars prefixed
VITE_toimport.meta.env, anything else silently stays server-only, which is what keepsenv.tssafe to import from client code
References
- Vite: env variables and modes —
VITE_prefix rules andimport.meta.envbehavior