Fix Vite Environment Variables Undefined in Production Builds
Quick Answer
- Root cause: Vite only exposes environment variables prefixed with
VITE_to client-side code, and production builds require variables to be defined in.env.production(or the mode-specific.envfile) — not just in.env.development. - First checks: Verify your variable names start with
VITE_(e.g.,VITE_API_URL), confirm.env.productionexists in the project root, and ensure no extra spaces or quotes in the file. - Minimal fix: Create a
.env.productionfile with all requiredVITE_variables, delete thedistfolder andnode_modules/.vitecache, then rebuild withvite build. - Environment boundary: This issue affects all Vite-based projects (React, Vue, Svelte, etc.) when switching from
vite devtovite build. Variables are statically replaced at build time, not at runtime.
What Problem It Solves
When you run vite dev, your environment variables from .env.development (or .env) are available via import.meta.env. But after running vite build and deploying the production output, those same variables may return undefined or empty strings. This happens because Vite's production build uses a different mode (production by default) and only loads the corresponding .env.production file. Without explicit configuration, variables defined only in development-mode files are absent in the production bundle.
Root Cause Analysis
Vite loads environment variables based on the current mode:
vite devdefaults to modedevelopment, loading.env.development(plus.envand.env.local).vite builddefaults to modeproduction, loading.env.production(plus.envand.env.local).
If you defined VITE_API_URL only in .env.development, it will be available during development but missing in the production build. Additionally, Vite strips any variable that does not start with VITE_ from client-side code — these are only available in server-side contexts via process.env.
Another common cause is stale build cache. Vite caches transformed modules in node_modules/.vite. If you change .env files without clearing this cache, the old variable values may persist.
Minimal Working Configuration
Project root structure:
my-project/
├── .env # loaded in all modes (lowest priority)
├── .env.local # loaded in all modes, git-ignored
├── .env.development # loaded only in development mode
├── .env.production # loaded only in production mode
├── .env.production.local # loaded only in production mode, git-ignored
├── vite.config.ts
└── src/
└── ...
Example .env.production:
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App (Production)
Example usage in code:
JAVASCRIPTconst apiUrl = import.meta.env.VITE_API_URL; console.log(import.meta.env.VITE_APP_TITLE);
Common Errors and Fixes
| Error | Likely Cause | Fix |
|---|---|---|
import.meta.env returns undefined | Variable not prefixed with VITE_, or missing from .env.production | Rename to VITE_* and add to .env.production |
Variable value is empty string or literal "undefined" | Incorrect .env file format (spaces, quotes, encoding) | Use VITE_KEY=value without extra spaces; wrap values with special characters in double quotes |
| Old variable values persist after change | Build cache not cleared | Delete dist/ and node_modules/.vite/, then rebuild |
Cannot read VITE_ variable in Node.js backend | VITE_ variables are client-side only | Use process.env with dotenv for server-side code |
Production Notes and Security Checks
- Never store secrets in
VITE_variables. Any value prefixed withVITE_is embedded in the client-side JavaScript bundle and visible to anyone who inspects the source code. This includes API keys, database credentials, and private tokens. - Use server-side proxies for sensitive operations. Have your backend expose an API endpoint that the frontend calls; the backend handles the actual sensitive request.
- Runtime configuration is not possible with
import.meta.env. If you need to change values after deployment (e.g., different API URLs per tenant), fetch configuration from a server endpoint at runtime instead. - CI/CD pipelines should ensure the correct
.env.productionfile is present before runningvite build, and that the build directory is cleaned between deployments to avoid stale cache.
FAQ
Q: Why do my VITE_ variables work in development but become undefined in production?
A: The most common reason is that the variables are defined only in .env.development (or .env) but not in .env.production. Vite loads mode-specific files during build. Create .env.production in your project root with all required VITE_ variables. Also check that the file name is exactly .env.production (not .env.prod or .env.production.local without the base .env.production file).
Q: Can I change VITE_ variables at runtime after the build?
A: No. Vite statically replaces import.meta.env.VITE_* with the actual values during the build step. After vite build completes, the values are hardcoded in the output files. To support runtime configuration, fetch settings from a server endpoint on application startup, or use server-side rendering (SSR) to inject variables into the HTML response.
Q: How do I safely handle API keys in a Vite production build?
A: Do not put sensitive keys in VITE_ variables. Instead, implement a backend API proxy: your frontend calls your own server endpoint, and that server holds the actual keys and makes the external API call. If you must expose a key (e.g., a public Firebase API key), ensure it is designed to be public and implement additional security like domain restrictions and rate limiting on the service provider side.