731 words
4 minutes

Vite Env Undefined in Production: Fix It at Build Time

2026-08-13
Frontend
Vite
/
Frontend
/
JavaScript
/
Troubleshooting

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#

SymptomLikely causeFirst fix
process.env.API_URL is undefined in browser codeNode-style access is being used in a Vite client moduleRead import.meta.env.VITE_API_URL
import.meta.env.API_URL is undefinedThe variable has no exposed prefixRename it to VITE_API_URL only if it is safe to expose
It works locally but not in productionThe production build did not receive the variableSet it before vite build and rebuild
It exists in .env.staging but not in the bundleThe build is using another modePass --mode staging or use the mode that the build actually selects
A secret appears in a client bundleA sensitive value was given a public prefixMove 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.com
DATABASE_PASSWORD=not-for-the-browser

Read the values like this:

const apiUrl = import.meta.env.VITE_API_URL;
const databasePassword = import.meta.env.DATABASE_PASSWORD;
console.log(apiUrl); // a string
console.log(databasePassword); // undefined in client code

The 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].local

For a staging build, make the mode explicit:

Terminal window
pnpm exec vite build --mode staging

Then place the public value in .env.staging or inject it into the build environment:

.env.staging
VITE_API_URL=https://staging-api.example.com

The 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 -> CDN

If 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:

Vite: Env Variables and Modes

Vite: envPrefix

Vite Env Undefined in Production: Fix It at Build Time
https://laplusda.com/en/posts/vite-env-undefined-production/
Author
Zero
Published at
2026-08-13
License
CC BY-NC-SA 4.0
Was this article useful?

Report a typo or broken link, or suggest a related topic.