How to Use Vite Env Variables in React: A Complete Guide to import.meta.env
Vite exposes environment variables through import.meta.env rather than process.env, requiring the VITE_ prefix for client-side variables and performing static replacement at build time for optimal performance.
Managing configuration across development and production environments is essential for modern React applications. When working with vite env variables, understanding Vite's unique approach to environment handling—implemented in the vitejs/vite repository—is crucial for secure and efficient code. Unlike Create React App or Node.js environments, Vite uses a compile-time replacement strategy that eliminates runtime overhead while protecting sensitive data.
How Vite Env Variables Work Under the Hood
Vite does not inject a process.env object into the client bundle. Instead, the framework reads .env* files on the server side, filters variables by a configurable prefix, and statically replaces references to import.meta.env with actual values during the build process.
The core implementation resides in packages/vite/src/node/plugins/define.ts, where the define plugin handles the replacement logic and respects the envPrefix configuration. During development, import.meta.env is injected as a real object for debugging purposes, while in production, each usage is compiled away to a literal string. TypeScript definitions in packages/vite/client.d.ts provide full IDE support and type safety for these variables.
Setting Up Vite Env Variables in React
Creating .env Files
Vite automatically loads environment files from the project root following a specific priority order: .env, .env.local, .env.[mode], and .env.[mode].local. The loadEnv implementation merges these files, with mode-specific local files taking highest precedence.
Create a .env file in your project root:
# .env (root of the project)
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My Vite React App
# This will NOT be exposed to the client
SECRET_TOKEN=super-secret
Important: After adding or modifying variables, restart the Vite dev server (npm run dev) to reload the environment files.
The VITE_ Prefix Requirement
By default, Vite only exposes environment variables that begin with VITE_ to the client-side code. This security measure prevents accidental leakage of database credentials, API keys, or other secrets to the browser. Variables without this prefix are available in Node.js contexts (such as vite.config.ts) but are stripped from the client bundle.
Accessing Vite Env Variables in React Components
React components access environment variables through the global import.meta.env object. This works in both JavaScript and TypeScript files without additional polyfills.
// src/App.tsx
import React from 'react';
export default function App() {
// The value is a string at runtime (dev) or a literal at build time
const apiUrl = import.meta.env.VITE_API_URL;
const title = import.meta.env.VITE_APP_TITLE;
return (
<div>
<h1>{title}</h1>
<p>API endpoint: {apiUrl}</p>
</div>
);
}
TypeScript users benefit from automatic type definitions provided in packages/vite/client.d.ts, which declares the ImportMetaEnv interface. This enables autocomplete and compile-time checking for environment variable names.
Customizing the Env Prefix
If VITE_ does not suit your naming conventions, you can configure a custom prefix via the envPrefix option in vite.config.ts. This configuration is documented in docs/config/shared-options.md.
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
// expose variables that start with APP_ instead of VITE_
envPrefix: 'APP_',
});
With this configuration, a variable named APP_FEATURE_FLAG=true in your .env file becomes accessible via import.meta.env.APP_FEATURE_FLAG. You can only specify one prefix at a time, and it must not be empty to prevent accidental exposure of all environment variables.
Using Env Variables Outside Components
Environment variables work identically in utility modules, API clients, or any other JavaScript/TypeScript file processed by Vite. The static replacement occurs across the entire source tree.
// src/utils/api.ts
export const fetchTodos = async () => {
const res = await fetch(`${import.meta.env.VITE_API_URL}/todos`);
return res.json();
};
The define plugin in packages/vite/src/node/plugins/define.ts ensures that import.meta.env.VITE_API_URL is replaced with the actual string literal during the build process, eliminating any runtime lookup overhead.
Security Best Practices
Vite's prefix filtering mechanism protects against common security pitfalls. However, you should follow these guidelines:
- Never remove the
envPrefixrestriction or set it to an empty string, as this would expose all environment variables (includingSECRET_TOKEN, database URLs, etc.) to the client bundle. - Keep sensitive keys in variables without the
VITE_prefix (or your custom prefix) when you need them only invite.config.tsor server-side code. - Remember that all values in
.envfiles are embedded into the client bundle if they match the prefix. Do not store user-specific secrets or session tokens there.
Summary
- Vite uses
import.meta.envinstead ofprocess.envfor client-side environment variables, with values replaced statically at build time by thedefineplugin inpackages/vite/src/node/plugins/define.ts. - Only variables prefixed with
VITE_(or your customenvPrefix) are exposed to the browser, protecting secrets from accidental leakage. - TypeScript definitions in
packages/vite/client.d.tsprovide full IDE support for autocompletion and type checking. - After modifying
.envfiles, restart the dev server to reload variables via theloadEnvimplementation. - Customize the prefix in
vite.config.tsifVITE_does not match your naming conventions.
Frequently Asked Questions
Can I use process.env in Vite?
No. Vite does not polyfill process.env for client-side code. Instead, you must use import.meta.env to access environment variables. This design choice enables static analysis and tree-shaking, as the define plugin replaces these references with literal strings during the build process.
Do I need to restart the dev server after changing .env files?
Yes. Vite loads environment files when the server starts using the loadEnv function. Changes to .env, .env.local, or mode-specific files are not hot-reloaded. You must stop and restart the development server (npm run dev) for new variables or modified values to become available in import.meta.env.
How do I type custom env variables in TypeScript?
Vite provides type definitions in packages/vite/client.d.ts that declare the ImportMetaEnv interface. To add types for your custom variables, create a vite-env.d.ts file in your src directory and use declaration merging:
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
This enables autocomplete and compile-time type checking for your vite env variables.
Why are my env variables undefined in production?
If variables are undefined in production but work in development, the issue is usually the missing VITE_ prefix (or your custom envPrefix). Vite strips all non-prefixed variables from the client bundle to prevent secret leakage. Verify that your variable starts with VITE_ in the .env file and that you reference it correctly as import.meta.env.VITE_YOUR_VAR in your code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →