Configure Vite build-time values with import.meta.env without leaking secrets
Ship different API URLs per environment in Vite by using VITE_ prefixed .env files, build-time replacement via import.meta.env, and safe defaults to avoid leaking secrets.
20 Aug 2025, 21:40 UTC

Problem: you need different API URLs and feature flags per environment, but Vite bundles code statically
Vite replaces environment variables at build time, not at runtime. The useful takeaway is to use VITE_ prefixed variables in .env files and read them via import.meta.env in your Vue or React source. The values are inlined as strings during dev server startup and production build, so the final bundle contains the literal value and the original variable name does not appear.
Desired outcome
A Vue or React Vite project that reads a public API base URL from the environment, switches automatically between development and production values, and never ships secret keys to the browser. Changes to .env files are reflected after a restart, and missing variables fail safely with a fallback.
Prerequisites
- A Vite project initialized with Vue or React. Vite provides import.meta.env as a build-time object.
- Node.js available in the project root where package.json lives.
- Access to create files at the project root. No special permissions are required beyond write access to the repo.
Create env files with the VITE_ prefix
Place .env files at the project root. Vite loads .env, then .env.[mode], with later files overriding earlier ones. Only variables prefixed with VITE_ are exposed to client code.
# .env
VITE_API_URL=https://api.example.com
VITE_APP_NAME=MyApp
# .env.development
VITE_API_URL=http://localhost:4000
# .env.production
VITE_API_URL=https://api.example.com
Risk: any variable with VITE_ is shipped to the browser in plain text. Do not prefix secret keys, private tokens, or database credentials with VITE_.
Use variables in source code
Access values through import.meta.env. Vite performs static replacement, so the reference is replaced with a string literal at build time.
// src/api.js
const apiBase = import.meta.env.VITE_API_URL || 'https://fallback.example.com'
export function getApiBase() {
return apiBase
}
In Vue SFCs the same access works in script blocks. Provide an inline fallback with || to avoid a runtime ReferenceError if the variable is missing, because Vite leaves an undefined reference unchanged when the variable is not defined.
For mode-specific builds, Vite selects the matching .env.mode file automatically. Run builds from the project root:
vite build --mode development
vite build --mode production
Verify replacement and mode selection
Start the dev server from the project root:
vite
Then log the value in the browser console. The printed value should match the value from the active .env file.
Inspect the production output. After running vite build, search the files in dist/ for the original variable name VITE_API_URL. It should not appear as a string, confirming static replacement occurred. The literal URL should be present instead.
Run a different mode to confirm switching:
vite build --mode staging
With a .env.staging file present, the built assets should contain values from that file and not from .env.production.
Confirm privacy by trying a non-prefixed variable. Accessing import.meta.env.SECRET_KEY will be undefined on the client and will not be replaced, demonstrating that only VITE_ prefixed vars are exposed.
Limitations and recovery options
Build-time only. Because replacement happens at build time, changing a .env file while the dev server is running does not hot-reload. Stop the server and restart vite to pick up changes.
SSR caveat. In SSR setups such as @vitejs/plugin-vue with server rendering, import.meta.env is client-only. Accessing it during server rendering returns undefined unless you explicitly pass values from the server environment.
If you see a ReferenceError for import.meta.env.VITE_*, the variable was missing at build time. Recovery is to add the variable to the appropriate .env file and restart the dev server, or add a safe fallback in code as shown above.
If a secret was accidentally prefixed with VITE_, remove the prefix, move the value to server-only configuration, and rebuild. Existing bundles already contain the secret in plain text and must be redeployed after the fix.
Do not rely on runtime environment changes. If you need values that can change without a rebuild, fetch them from a server endpoint after the app loads instead of using import.meta.env.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.