Custom modes (staging, qa)
Surface non-default Vite modes to browser code. Why the envConfig() plugin is required even if you load config via dynamic import.
The problem
You run vite build --mode staging and expect envName() to say "staging" in the browser. It won't — and the reason is subtle. In the browser, envName()'s readEnv() only reads window.__env (or the <EnvScript /> tag), never process.env. A pure SPA has neither, so NODE_ENV and VITE_ENV are invisible and envName() falls through to its "development" fallback — no matter what --mode you built with. (Vite even forces NODE_ENV="production" at build time, but that only ever reaches envName() on the server, not in browser code.)
This is independent of how you load config: it bites both Pattern 1 (dynamic import) and any browser code that calls envName() directly.
The fix
The envConfig() plugin injects __ENV_NAME__ = JSON.stringify(mode) as a build-time constant. envName() checks __ENV_NAME__ before NODE_ENV in its precedence chain, so the browser sees the mode you actually built.
import { envConfig } from "@vlandoss/env/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [envConfig()],
});That's all the plugin needs to do for this case — you don't have to import #config or do anything else.
Building and running custom modes
vite build --mode staging
vite build --mode qa
vite build --mode productionPlace a matching config file per mode under [src/]config/:
src/
config/
development.ts
staging.ts
qa.ts
production.tsThe plugin's discovery scans [src/]config/<mode>.{ts,mts,cts,js,mjs,cjs,json} and pulls in the right one — same algorithm as loadConfig in @vlandoss/env/fs.
Selecting the env without --mode
If you'd rather not thread --mode through every command, set a VITE_ENV env var instead. The plugin reads it from process.env and your .env* files (via Vite's loadEnv, so an inline/shell value wins over a file value) and uses it to pick the config file and __ENV_NAME__. These are equivalent:
VITE_ENV=staging vite build
vite build --mode stagingVITE_ENV takes precedence; when it's unset or empty the plugin falls back to Vite's mode, so --mode keeps working unchanged. Put it in a .env file to make it the default for a project:
VITE_ENV=stagingRename the var with the envVar option if VITE_ENV clashes with something:
export default defineConfig({
plugins: [envConfig({ envVar: "APP_ENV" })],
});Verifying it works
After a vite build --mode staging, anywhere in browser code:
import { envName } from "@vlandoss/env";
console.log(envName()); // "staging"And env.$name on the resolved env object will also be "staging".
Reference example
examples/spa-vite-dynamic/ wires the plugin purely for the __ENV_NAME__ inject — its config is loaded by a dynamic import(), not by the #config alias. The plugin earns its place in the config solely to make envName() honest after a build.
Server vs. browser
On the server, envName() reads process.env.ENV (highest precedence) or process.env.NODE_ENV. There's no __ENV_NAME__ involved — Node doesn't have Vite's NODE_ENV quirk. So custom modes "just work" on the server side as long as you set ENV=staging (or NODE_ENV=staging) before running the process.