# SPA — Vite plugin (#config) (/docs/guides/spa-vite-plugin)



## When to use this [#when-to-use-this]

* Pure SPA built with Vite.
* You want **each build artifact to ship only its env's config** — other env files must not be present in the deployment at all.
* You're OK with one build per environment, selected via `--mode <env>` or a [`VITE_ENV` env var](/docs/guides/custom-modes#selecting-the-env-without---mode).

If a single multi-env build is acceptable, the simpler [dynamic-import pattern](/docs/guides/spa-dynamic-import) is the default.

## Reference example [#reference-example]

[`examples/spa-vite-plugin/`](https://github.com/variableland/env/tree/main/examples/spa-vite-plugin) — React + Vite SPA importing config through the `#config` alias.

## Wiring [#wiring]

```ts title="vite.config.ts"
import react from "@vitejs/plugin-react";
import { envConfig } from "@vlandoss/env/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [react(), envConfig()],
});
```

```ts title="src/env/schema.ts"
import { type Config, schema } from "@vlandoss/env";
import * as e from "@vlandoss/env/zod";
import * as z from "zod";

export const Env = schema({
  api: { BASE_URL: z.url(), TIMEOUT_MS: z.coerce.number().int().positive().default(5000) },
  feature: { ANALYTICS: e.bool.default(false) },
  build: { LABEL: z.string().min(1) },
});

export type EnvConfig = Config<typeof Env>;
```

```ts title="src/env/config.d.ts"
declare module "#config" {
  import type { EnvConfig } from "./schema.ts";
  const config: EnvConfig;
  export default config;
}
```

```ts title="src/env/index.ts"
import config from "#config";
import { defineEnv } from "@vlandoss/env";
import { Env } from "./schema.ts";

export const env = defineEnv({ schema: Env, config });
```

```ts title="src/config/production.ts"
import type { EnvConfig } from "../env/schema.ts";

export default {
  api: { BASE_URL: "https://api.example.com", TIMEOUT_MS: 8000 },
  feature: { ANALYTICS: true },
  build: { LABEL: "prod-build-marker-b71c" },
} satisfies EnvConfig;
```

## Building per env [#building-per-env]

```bash title="terminal"
vite build --mode development
vite build --mode production
vite build --mode staging
```

Each command produces a separate `dist/` whose bundle contains **only** the matching `config/*.ts`. Prefer an env var over `--mode`? `VITE_ENV=staging vite build` is equivalent — see [Selecting the env without `--mode`](/docs/guides/custom-modes#selecting-the-env-without---mode).

## What each piece does [#what-each-piece-does]

* **`envConfig()` plugin** registers `resolve.alias["#config"]` pointing at `[src/]config/<env>.{ts,mts,cts,js,mjs,cjs,json}` (same discovery algorithm as `loadConfig`). The `<env>` is `VITE_ENV` (from `process.env` or `.env*`) falling back to Vite's `mode`. It also injects `__ENV_NAME__ = JSON.stringify(env)` so `envName()` returns the right env in the browser — see [Custom modes](/docs/guides/custom-modes).
* **`#config.d.ts`** declares the type of the alias for TypeScript. Vite resolves the runtime import; this just stops `tsc` from complaining.
* **`defineEnv({ schema, config })`** runs synchronously here — `config` is a plain object, not a Promise.

## Plugin options [#plugin-options]

```ts
envConfig({
  alias: "#config",       // default
  cwd: process.cwd(),     // default — base directory for discovery
  envVar: "VITE_ENV",     // default — env var that selects the env (falls back to `mode`)
});
```

When no file matches the current env, the alias resolves to a virtual module that throws **only if `#config` is actually imported** — so tools that introspect the Vite config (Vitest's IDE-driven discovery, third-party plugins) don't trip over a config-time error.

## Tradeoffs [#tradeoffs]

* **One build per env.** Your CI/CD needs to run a build (`vite build --mode <env>`, or `VITE_ENV=<env> vite build`) for every environment you ship, and produce a separate artifact for each.
* **No runtime switching.** Once built, the artifact is locked to its env. If you need to swap envs without rebuilding, use the [dynamic-import pattern](/docs/guides/spa-dynamic-import).
