Vite env variables undefined in production: causes and fixes
By Brook Solomon, Founder, EthioDeploy
A Vite env variable is undefined in production when the build never saw it. Vite replaces every import.meta.env.VITE_ reference with a fixed value while vite build runs, so a variable added to your server afterwards changes nothing until you build again. The other common causes are a name without the VITE_ prefix and code that reads process.env in the browser.
Why is my Vite env variable undefined in production?
Because Vite reads it once, during the build. The Vite docs say import.meta.env constants are “defined as global variables during dev and statically replaced at build time.” The built JavaScript holds the literal value. If the variable was missing when vite build ran, the built code has nothing to read.
That is why an app can work on your laptop and break after deploy. Your laptop has a .env.local file. The machine that builds your app usually doesn't.
// src/api.ts, what you write const apiUrl = import.meta.env.VITE_API_URL // dist/assets/index-*.js, what ships after the build const apiUrl = "https://api.example.com"
Quick checklist
| What you see | Cause | Fix |
|---|---|---|
| Works locally, undefined after deploy | The value lives in .env.local, which git ignores, so the build server never had it | Set the variable in your host's settings, then rebuild |
| Undefined everywhere, even locally | The name has no VITE_ prefix | Rename it to VITE_* and restart the dev server |
process is not defined in the browser console | Browser code reads process.env | Read import.meta.env instead |
| Old value still shows after you changed it | The built files still hold the old value | Redeploy so vite build runs again |
Undefined inside vite.config.ts | .env files aren't loaded into process.env while the config runs | Use loadEnv |
"3000" + 1 gives "30001" | Every value is a string | Convert it with Number(), or compare to "true" |
Does the variable name start with VITE_?
Only variables that start with VITE_ reach your client code. Everything else is left out on purpose, so a database password in the same .env file never lands in the browser. This is the example from the Vite docs:
# .env VITE_SOME_KEY=123 DB_PASSWORD=foobar
console.log(import.meta.env.VITE_SOME_KEY) // "123" console.log(import.meta.env.DB_PASSWORD) // undefined
Moving from Create React App? REACT_APP_ names are not exposed. Rename them, or add the old prefix with the envPrefix option, which takes a string or an array:
// vite.config.ts
export default defineConfig({
envPrefix: ["VITE_", "REACT_APP_"],
})Never set envPrefix to an empty string. That would expose every variable in the environment, and Vite throws an error if you try.
Was the variable set when vite build ran?
The machine that runs vite build needs the variable. Setting it later on the server that serves the files does nothing for VITE_ values. Three details decide whether the build sees it:
.env.localand.env.*.localare meant to stay out of git, so a CI runner or a hosting platform never sees them.vite buildruns inproductionmode by default, so it loads.env,.env.local,.env.productionand.env.production.local. A.env.developmentfile is ignored.- Variables already set in the environment win over every
.envfile. SettingVITE_API_URLin your host's settings works even if a committed.envhas a different value.
With Docker, pass the value as a build argument. Setting it with docker run -e is too late, because the image already holds the built files.
FROM node:22 AS build WORKDIR /app COPY . . ARG VITE_API_URL ENV VITE_API_URL=$VITE_API_URL RUN npm ci && npm run build
docker build --build-arg VITE_API_URL=https://api.example.com .
Are you reading process.env in browser code?
Read import.meta.env instead. process is a Node.js global, and a Vite app doesn't have it in the browser, so process.env.VITE_API_URL usually fails with ReferenceError: process is not defined. This one is common in code copied from Create React App or webpack projects, and in AI-generated code.
Why is the value undefined in vite.config?
Because Vite reads your .env files after it has loaded the config. The Vite docs say those files are “not automatically injected into process.env while your vite.config.* is running.” Load them yourself with loadEnv:
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// '' loads every variable, whatever its prefix
const env = loadEnv(mode, process.cwd(), '')
return {
define: { __APP_ENV__: JSON.stringify(env.APP_ENV) },
}
})Why does my number or boolean act like a string?
Every value in import.meta.env is a string, including 123 and true. Convert it where you read it:
const port = Number(import.meta.env.VITE_PORT) const debug = import.meta.env.VITE_DEBUG === "true"
How to fix it on EthioDeploy
- Open the project, go to Environment Variables and add the variable with its
VITE_name. - Leave “Runtime only” off. Runtime-only variables are kept out of the build, so a
VITE_value marked that way is always undefined in the browser. - Redeploy. A changed value takes effect on the next build.
Apps exported from Lovable and Bolt hit this often, because their Supabase keys sit in a .env file that isn't in the repo. See Deploy a Lovable app and Environment variables.
Questions
Can I change a Vite env variable without rebuilding?
No. The value is written into the built JavaScript, so a change needs a new vite build. If you need settings that change without a rebuild, serve them from your backend or from a config.json file the app fetches when it starts.
Is it safe to put an API key in a VITE_ variable?
No. VITE_ values are bundled into the JavaScript your visitors download, so anyone can read them. Keep secret keys on a server and call that server from the app. Public keys that are meant for the browser, like a Supabase publishable key, are fine.
Does vite build read .env.local?
Yes, .env.local is loaded in every mode. But it is normally listed in .gitignore, so it exists on your laptop and not on the build server. Set the same values in your host's environment settings.
Which .env file does vite build use?
vite build runs in production mode, so it loads .env, .env.local, .env.production and .env.production.local. Mode-specific files win over generic ones, and variables already set in the environment win over all of them.
Why is my env variable undefined in vite.config.ts?
Vite loads .env files after the config runs, so they are not in process.env yet. Call loadEnv(mode, process.cwd(), '') inside defineConfig to read them.
Sources
Checked on October 29, 2026:
Push a repo. Get a URL.
Start free