Vite Env Variables Missing in Build? Fix It and Deploy Safely on CM Cloud
If your Vite app works perfectly in dev but shows undefined for every environment variable in production, you have hit the most common Vite deployment trap — and it is fixable in under five minutes once you understand why it happens.
This guide explains the root cause, the correct fix, and how CM Cloud App Hosting makes build-time environment variables a first-class concern rather than an afterthought.
Why Vite Env Variables Go Missing in Production
Vite is a build tool, not a Node.js runtime. When you run vite build, it bundles your source code into static files. At that moment — build time — Vite performs a string-replacement pass over your code. Any variable prefixed with VITE_ in your .env file gets inlined directly into the bundle as a literal string.
What this means in practice:
- If the variable exists at build time → it is baked into the bundle → it works.
- If the variable is only available at runtime on the server → Vite never saw it →
import.meta.env.VITE_MY_VARisundefined.
This is why a variable defined in your .env.local on your laptop works fine, but the same variable set only in your hosting panel — after the build runs — is invisible to the browser bundle.
The VITE_ Prefix Rule
Vite deliberately ignores any variable that does not start with VITE_. This is a security boundary — it prevents secret server-side values (database passwords, API keys) from accidentally leaking into client-side JavaScript.
Variables without the VITE_ prefix are available in vite.config.js and server-side SSR code only. They are never injected into the browser bundle.
| Variable name | Available in browser bundle? | Available in vite.config.js? |
|---|---|---|
VITE_API_URL |
✅ Yes (string-replaced at build) | ✅ Yes |
API_SECRET_KEY |
❌ No | ✅ Yes |
NODE_ENV |
❌ No (use import.meta.env.MODE) |
✅ Yes |
The Three Ways Variables Go Missing
1. Variable Set After the Build Runs
You add VITE_API_URL to your server environment panel, then deploy. The CI/CD pipeline runs vite build — but the build step runs before (or without access to) the variables you set post-deploy. Solution: ensure all VITE_ variables are injected into the build environment, not just the runtime environment.
2. Missing VITE_ Prefix
You name the variable API_URL instead of VITE_API_URL. Vite ignores it entirely during the bundle pass. Rename the variable and rebuild.
3. .env File Not Committed or Not Loaded
Vite loads .env, .env.local, .env.[mode], and .env.[mode].local. If you rely on .env.local (which should be in .gitignore), that file never reaches your CI runner. Use .env.production for values that are not secret and need to travel with the repo, or inject secrets through your CI pipeline.
The Correct Pattern: Build-Time Injection
For any variable that must appear in a Vite browser bundle, inject it into the environment before vite build runs.
In a typical CI workflow:
# GitHub Actions example
- name: Build Vite app
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_PUBLIC_KEY: ${{ secrets.VITE_PUBLIC_KEY }}
run: npm run build
The variable exists in the shell that runs vite build, Vite picks it up, and it is inlined into the bundle. No runtime server needed to serve these values — they are already in the JavaScript.
Separate Concerns Clearly
VITE_prefix → safe for browsers, inlined at build, visible to end users. Never put secrets here.- No prefix → server-only, never reaches the browser. Put API keys, DB passwords, and signing secrets here.
How CM Cloud App Hosting Handles This
CM Cloud App Hosting separates build-time and runtime environment variables cleanly through the portal.
When you push code and the CI/CD deploy hook fires, CM Cloud pulls your latest commit and runs your build command (e.g. npm run build). Variables you mark as build-time in the portal are injected into that build shell — so Vite sees them exactly as if you had set them in a GitHub Actions env: block.
Variables you mark as runtime-only are available to your Node.js server process (Express, Next.js server components, FastAPI, Django) but are never exposed during the static bundle step.
This means:
VITE_API_URL→ set as build-time → inlined by Vite ✅DATABASE_URL→ set as runtime-only → available to your server process, never in the browser bundle ✅
For a full walkthrough of setting secrets in the portal, see the published guide: App Hosting Environment Variables: Deploy Secrets Safely on CM Cloud.
All plans include the CI/CD deploy hook — generate a secret URL in the portal, POST to it from GitHub Actions, and your app redeploys on every git push. No SSH required.
CM Cloud App Hosting vs Competitors
| Provider | Starter Price (USD) | XAF | EUR | Build-time env vars | CI/CD hook included |
|---|---|---|---|---|---|
| CM Cloud App Hosting | $7.99/mo | XAF 5,000 | €7.40 | ✅ Separate build + runtime | ✅ Yes |
| Railway (Hobby) | ~$5/mo usage-based | ~XAF 3,100 | ~€4.60 | ✅ Yes | ✅ Yes |
| Render (Starter) | $7/mo | ~XAF 4,350 | ~€6.50 | ✅ Yes | ✅ Yes |
| DigitalOcean App Platform | $5/mo (basic static) | ~XAF 3,100 | ~€4.60 | ✅ Yes | ✅ Yes |
Railway and Render are strong developer tools, but both price in USD with no XAF billing option and no African-market support focus. CM Cloud is the only provider billing in XAF, EUR, and USD simultaneously — critical for Cameroonian and West African businesses managing foreign exchange.
See full plan pricing at cmcloudhosting.com/pricing.
Supported Runtimes on CM Cloud App Hosting
CM Cloud manages the full runtime layer — nginx, systemd, process supervision — across:
- Node.js — Express, Next.js
- Python — FastAPI, Django, Flask
- PHP — Laravel
- PocketBase — realtime backend with auth, file storage, and admin dashboard
You push code. CM Cloud handles the rest. No server configuration, no SSH.
PocketBase in particular pairs cleanly with a Vite + React or Vite + Vue frontend: your VITE_POCKETBASE_URL is injected at build time, the PocketBase backend runs as its own App Hosting instance, and you get realtime subscriptions, OAuth, and file storage — the full Supabase experience without a separate Supabase subscription.
Quick Diagnostic Checklist
If import.meta.env.VITE_MY_VAR is undefined in production, run through this list:
- [ ] Does the variable name start with
VITE_? - [ ] Is the variable available in the build shell (not just runtime)?
- [ ] Are you reading it as
import.meta.env.VITE_MY_VAR(notprocess.env)? - [ ] Did you rebuild after adding the variable? (It must be inlined at build time — changing it on the server does not update the bundle.)
- [ ] Is
.env.localin.gitignoreand therefore absent from CI? Move non-secret values to.env.productionor inject via CI secrets.
Plan Comparison
| Plan | Price (USD) | XAF | EUR | vCPU | RAM |
|---|---|---|---|---|---|
| App Hosting Starter | $7.99/mo | XAF 5,000 | €7.40 | Managed | Managed |
| Cloud VPS Starter | $7.99/mo | XAF 5,000 | €7.40 | 2 vCPU | 4 GB |
If you need full root access and want to manage your own Node.js process manually, the Cloud VPS gives you a full Linux VM. If you want to push code and let CM Cloud handle runtime supervision, CI/CD, and process management, App Hosting is the right product. Both start at the same price.
Compare all options at cmcloudhosting.com/pricing.
Deploy Your Vite App on CM Cloud Today
Stop losing hours to undefined environment variables in production. CM Cloud App Hosting injects your build-time variables correctly, runs your vite build in a clean shell with your secrets available, and redeploys on every git push via the CI/CD hook.
Starting at $7.99/mo (XAF 5,000 / €7.40) — no multi-year lock-in, no promotional bait-and-switch pricing.