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 envPrefix restriction or set it to an empty string, as this would expose all environment variables (including SECRET_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 in vite.config.ts or server-side code.
  • Remember that all values in .env files 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.env instead of process.env for client-side environment variables, with values replaced statically at build time by the define plugin in packages/vite/src/node/plugins/define.ts.
  • Only variables prefixed with VITE_ (or your custom envPrefix) are exposed to the browser, protecting secrets from accidental leakage.
  • TypeScript definitions in packages/vite/client.d.ts provide full IDE support for autocompletion and type checking.
  • After modifying .env files, restart the dev server to reload variables via the loadEnv implementation.
  • Customize the prefix in vite.config.ts if VITE_ 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →