typescript 69 lines · 3 tabs

Vite env handling: explicit prefixes only

Shared by codesnips Jan 2026
3 tabs
import { z } from "zod";

export const envSchema = z.object({
  VITE_API_URL: z.string().url(),
  VITE_APP_NAME: z.string().min(1).default("my-app"),
  VITE_ENABLE_ANALYTICS: z
    .enum(["true", "false"])
    .default("false")
    .transform((v) => v === "true"),
  VITE_LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
  VITE_SENTRY_DSN: z.string().url().optional(),
});

export type AppEnv = z.infer<typeof envSchema>;
3 files · typescript Explain with highlit

Vite only exposes environment variables prefixed with VITE_ to client code, silently dropping anything else from import.meta.env. That quiet behavior is a common source of bugs: a developer references import.meta.env.API_URL, gets undefined at runtime, and never sees a warning. This snippet centralizes and hardens env access so misconfiguration fails loudly at build/startup time rather than surfacing as broken behavior in production.

The env schema tab defines the contract. It uses zod to declare exactly which VITE_-prefixed keys the app expects, along with their types and coercions — VITE_API_URL must be a valid URL, VITE_ENABLE_ANALYTICS is coerced from the string "true"/"false" into a real boolean, and VITE_LOG_LEVEL is constrained to an enum. Because Vite injects everything as strings, coercion is not optional; the schema is where raw strings become the typed values the rest of the code relies on.

The parseEnv helper tab consumes import.meta.env and runs it through the schema. It deliberately checks for any keys the code touches that lack the VITE_ prefix and throws a descriptive error, turning Vite's silent omission into an explicit failure. On a ZodError it formats each issue with its path and message, so a missing or malformed variable produces a readable message naming the offending key instead of a cryptic undefined. The parsed result is frozen and exported once as env, giving the app a single validated source of truth.

The vite.config.ts tab shows the build-time side. loadEnv reads .env files for the active mode, and an explicit assertion rejects any variable that looks app-facing but is missing the prefix, catching typos like VIT_API_URL before the dev server even starts. envPrefix is pinned to VITE_ to make the boundary intentional rather than implicit.

The trade-off is a small amount of upfront ceremony per variable, but it buys compile-time types via z.infer, fail-fast validation, and autocomplete on env. This pattern is worth reaching for on any non-trivial frontend where a wrong or missing config value would be expensive to discover late.


Related snips

Share this code

Here's the card — post it anywhere.

Vite env handling: explicit prefixes only — share card
Link copied