Vite has a specific, security-conscious approach to environment variables: import.meta.env instead of process.env, a required VITE_ prefix for client exposure, and mode-specific .env files. This lesson covers how to configure a React app safely across development, staging, and production.
1Configuration Without Hardcoding
Applications typically need different configuration values across environments — a local API URL in development versus a production URL when deployed. Environment variables let this configuration vary without changing source code, and Vite exposes them to client code through a deliberately restricted mechanism.
2import.meta.env, Not process.env
Since browsers have no process object, Vite exposes environment variables through import.meta.env, statically replacing references to it at build time. This differs from tools like Create React App, which polyfilled process.env.REACT_APP_X through webpack.
3The VITE_ Prefix Requirement
Only variables prefixed with VITE_ are exposed to client-side code — this is a deliberate security boundary that prevents accidentally shipping a server-only secret, like a database password, to the browser simply because it existed somewhere in a .env file. Unprefixed variables remain server-only.
4Environment-Specific .env Files
Vite loads different .env files depending on the current mode: .env for all modes, .env.development for local development, .env.production for production builds, and .env.local variants that are git-ignored by default for machine-specific overrides. More specific files take precedence over less specific ones.
5Remember: Client Env Vars Are Never Truly Secret
A VITE_-prefixed variable is compiled directly into the JavaScript bundle shipped to every visitor, so anyone can read it via browser developer tools. Client-exposed variables should only ever hold genuinely public configuration, like a public API base URL, never real secrets like private keys or credentials.
6Step-by-Step Breakdown
Configuration Without Hardcoding. Your app needs different values in different environments — a local API URL in development, a staging URL in QA, a production URL when deployed. Environment variables let you swap these values without touching source code, and Vite has a specific, security-conscious way of exposing them to your client-side app.
import.meta.env, Not process.env. In a Vite project, you read environment variables through import.meta.env, not Node's process.env — the browser has no process object, and Vite statically replaces import.meta.env.X references at build time. This is different from Create React App, which used process.env.REACT_APP_X via a webpack polyfill.
Why can't you read a Node-style process.env.MY_VAR value directly in Vite client code?
- →The browser has no process object; Vite uses import.meta.env instead
- →process.env was removed from JavaScript entirely
The VITE_ Prefix Requirement. Only variables prefixed with VITE_ are exposed to your client-side code. This is a deliberate security boundary: it prevents you from accidentally shipping a secret server-side key (like a database password) to the browser just because it happened to be in your .env file. Variables without the prefix stay server-only.
Environment-Specific .env Files. Vite automatically loads different files depending on the current mode: .env for all modes, .env.development for dev, .env.production for the production build, and .env.local variants (git-ignored by default) for machine-specific overrides like a personal API key. More specific files override less specific ones.
Which file is the right place for a personal, machine-specific API key that should never be committed to git?
- →.env.local, which is git-ignored by default
- →.env.production, committed alongside the code
Remember: Client Env Vars Are Never Truly Secret. Even a VITE_-prefixed variable is baked directly into the JavaScript bundle shipped to every visitor's browser — anyone can open dev tools and read it. Only use client-exposed environment variables for genuinely public configuration (API base URLs, public keys), never for real secrets like private API keys or database credentials.
Mastery Achieved. You now know how to configure a Vite React app across environments: reading values through import.meta.env, understanding why the VITE_ prefix exists as a security boundary, using mode-specific .env files, and remembering that exposed variables are never truly secret. Next, you'll set up path aliases to clean up your import statements.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
import.meta.env values are statically replaced at build time, producing plain browser-compatible JavaScript.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Environment Config Doesn't Directly Affect Accessibility
Environment variables control configuration values, not markup or interaction patterns, so they have no direct accessibility implications — but be mindful that feature flags driven by env vars can enable/disable UI that does need its own accessibility review.
SEO Implications
- 1
Correct Environment Config Prevents Broken Canonical URLs
If a base URL used for canonical links or Open Graph tags is misconfigured per environment (e.g. a staging URL leaking into a production build), it can cause crawlers to index the wrong domain — mode-specific .env files help keep this correct per deployment target.
Best Practices
Never Prefix Real Secrets with VITE_
Treat the VITE_ prefix as an explicit opt-in to public exposure — audit every VITE_-prefixed variable and confirm it's safe for any visitor to read in their browser's developer tools.
Commit .env.example, Never Commit .env.local
Keep a checked-in .env.example listing required variable names with placeholder values, while .env.local (containing real personal or sensitive values) stays git-ignored.
Frequent Bugs
A new environment variable reads as undefined even though it's in the .env file.
Confirm the variable name starts with VITE_ — unprefixed variables are intentionally excluded from import.meta.env in client code. Also restart the dev server after adding a new variable, since Vite reads .env files at startup.
A secret API key meant for server-side use only shows up in the browser bundle.
The variable was accidentally prefixed with VITE_, which explicitly exposes it to client code. Remove the prefix and access it only from server-side code, never from a Client Component or browser-executed module.
Real-World Examples
Configuring a Multi-Environment API Base URL
A team needed the app to hit a local mock server in development, a staging API during QA, and the real production API once deployed. They defined VITE_API_URL in .env.development, .env.staging (loaded via --mode staging), and .env.production respectively, and referenced it once in a shared api client module.
// src/api/client.ts
const BASE_URL = import.meta.env.VITE_API_URL;
export function fetchUser(id: string) {
return fetch(`${BASE_URL}/users/${id}`);
}