Vite Env Undefined in Production: Fix It at Build Time
When a Vite environment variable works in development but is undefined after deployment, check the build boundary first. Client code should read import.meta.env.VITE_NAME, the variable must be available to the build, and changing a deployment variable after static assets were built requires another build.
Vite exposes environment variables as strings and statically replaces them during bundling. That is why a browser bundle cannot automatically read a new server process environment after the files have already been generated.
Check the five common boundaries
| Symptom | Likely cause | First fix |
|---|---|---|
process.env.API_URL is undefined in browser code | Node-style access is being used in a Vite client module | Read import.meta.env.VITE_API_URL |
import.meta.env.API_URL is undefined | The variable has no exposed prefix | Rename it to VITE_API_URL only if it is safe to expose |
| It works locally but not in production | The production build did not receive the variable | Set it before vite build and rebuild |
It exists in .env.staging but not in the bundle | The build is using another mode | Pass --mode staging or use the mode that the build actually selects |
| A secret appears in a client bundle | A sensitive value was given a public prefix | Move the secret behind a server or serverless endpoint |
The Vite environment and mode guide is the source of truth for these loading rules. Do not debug all five boundaries by changing the deployment platform at once; verify them in order.
Use import.meta.env and expose only public values
Given this .env file:
VITE_API_URL=https://api.example.comDATABASE_PASSWORD=not-for-the-browserRead the values like this:
const apiUrl = import.meta.env.VITE_API_URL;const databasePassword = import.meta.env.DATABASE_PASSWORD;
console.log(apiUrl); // a stringconsole.log(databasePassword); // undefined in client codeThe VITE_ prefix is an exposure rule, not a security mechanism. Anything exposed under that prefix is bundled into code that a visitor can download. API keys, database passwords, private tokens, and signing secrets do not belong in VITE_* variables.
If your project needs a different public prefix, configure Vite’s envPrefix deliberately and review the result as part of a security check. A broader prefix makes accidental exposure easier.
Match the .env file to the build mode
Vite loads the general files and then the mode-specific files:
.env.env.local.env.[mode].env.[mode].localFor a staging build, make the mode explicit:
pnpm exec vite build --mode stagingThen place the public value in .env.staging or inject it into the build environment:
VITE_API_URL=https://staging-api.example.comThe mode is a build input. If a CI job runs the default production build, .env.staging will not be the file that supplies its values. Existing process environment variables also take precedence, so a stale CI variable can override a correct file.
Treat static deployment variables as build inputs
For a static Vite site, this sequence matters:
environment variables -> vite build -> JavaScript and HTML assets -> CDNIf you change VITE_API_URL in a hosting dashboard after the build has finished, the old bundle remains unchanged until the build runs again. Rebuild and redeploy, then verify the generated site rather than only checking the dashboard setting.
When the value must change at runtime without rebuilding, use a runtime configuration endpoint or server-rendered value instead of pretending a static client bundle has live access to the host environment. Keep the runtime response limited to values that are safe for the browser.
Make missing configuration fail clearly
Do not wait for a production request to reveal a missing URL. Centralize the check:
const apiUrl = import.meta.env.VITE_API_URL;
if (!apiUrl) { throw new Error('VITE_API_URL is missing from this build');}
export const config = { apiUrl };For a more defensive build, add a CI check that prints the variable name and whether it exists, never its secret value. The check should run before the bundler so the failure points to configuration instead of a later network error.
If the project is an Astro app, keep the Vite client/server boundary in mind: browser-exposed values and server-only values do not have the same safety requirements. The site’s Vite manualChunks migration guide is a related build-time troubleshooting example, while Astro’s base-path guide covers a different static-build boundary.
FAQ
Why is a Vite variable undefined only after deployment?
The production build likely did not receive the variable, the name lacks the VITE_ prefix, or the build used a different mode. Check the final build command and its environment first.
Do I need to restart Vite after editing .env?
Restart the dev server, and rebuild for production. Environment values are loaded as part of the Vite process and are replaced into the output during bundling.
Can I put an API secret in VITE_API_KEY?
No. A VITE_ value is intended for client exposure. Keep secrets on a server or serverless function and let the browser call a controlled endpoint.
References:
Report a typo or broken link, or suggest a related topic.