Skip to content

Recipes

Idiomatic compositions of @mongez/vite features. Drop the snippets into your vite.config.ts (and the matching .env files) and tweak.

Minimal SPA setup

vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mongezVite from "@mongez/vite";
export default defineConfig({
plugins: [
mongezVite(), // everything default-on except htaccess + preRender
react(),
],
});
.env.development
APP_NAME="My App"
APP_PORT=3000
# .env.production
APP_NAME="My App"
PUBLIC_URL=https://cdn.example.com/
index.html
<title>__APP_NAME__</title>

What happens:

  • vite dev: loads .env.development, replaces __APP_NAME__ with My App, opens the browser, mirrors tsconfig paths.
  • vite build: loads .env.production, sets config.base = "https://cdn.example.com/", replaces __APP_NAME__, zips dist/.

Production CDN base URL via env

vite.config.ts
import mongezVite from "@mongez/vite";
export default defineConfig({
plugins: [
mongezVite({
envBaseUrlKey: "CDN_HOST",
}),
],
});
.env.production
CDN_HOST=https://assets.example.com
Terminal window
vite build # → assets referenced from https://assets.example.com/

Apache deployment with .htaccess + prerender

mongezVite({
htaccess: true,
preRender: {
url: "https://render.mentoor.io",
cache: true,
},
});

After vite build:

dist/
├── assets/
│ └── ...
├── index.html
├── .htaccess (SPA rewrites + Googlebot routing)
├── prerender.php
└── build.zip

Drop the zip on an Apache box, unzip into the docroot, and SPA routing + crawler pre-rendering both work without further config.

Multi-stage builds (staging / production / preprod)

vite.config.ts
import mongezVite from "@mongez/vite";
export default defineConfig({
plugins: [
mongezVite({
productionEnvName: process.env.STAGE,
}),
],
});
Terminal window
STAGE=staging vite build # loads .env.staging
STAGE=preprod vite build # loads .env.preprod
STAGE=production vite build # loads .env.production

Without STAGE, productionEnvName is undefined and the plugin falls through to the default chain (.env.production.env.build.env).

Disable everything except env + base + index.html

mongezVite({
autoOpenBrowser: false,
linkTsconfigPaths: false,
compressBuild: false,
htaccess: false,
});

The plugin reduces to: load env, set config.base during build, replace __KEY__ tokens in index.html. Useful when you already have other plugins handling tsconfig paths, compression, and dev-server behaviour.

Custom html token delimiters

mongezVite({
htmlEnvPrefix: "<!-- ",
htmlEnvSuffix: " -->",
});
<title><!-- APP_NAME --></title>

Use HTML-comment-shaped tokens when your HTML is processed by a tool that strips __double_underscores__ (rare, but it happens).

Custom build artifact name

mongezVite({
compressedFileName: () => {
const tag = process.env.GIT_TAG || "latest";
return `myapp-${tag}.zip`;
},
});
Terminal window
GIT_TAG=v1.4.2 vite build
# → dist/myapp-v1.4.2.zip

For an async lookup:

mongezVite({
compressedFileName: async () => {
const tag = await readVersionFromS3();
return `myapp-${tag}.zip`;
},
});

Layered env with shared defaults

.env.shared
APP_NAME="My App"
APP_URL=https://example.com
# .env.development
DB_HOST=localhost
DEBUG=true
# .env.production
DB_HOST=prod-db.example.com
DEBUG=false
PUBLIC_URL=https://cdn.example.com/
mongezVite();

What happens:

  • vite dev: .env.shared loads first, then .env.development. APP_NAME survives from shared; DB_HOST and DEBUG come from dev.
  • vite build: .env.shared first, then .env.production. Same idea.

This is @mongez/dotenv’s default — loadEnv is called with loadSharedEnv: true.

SPA + tsconfig paths

tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@lib": ["src/lib/index.ts"]
}
}
}
mongezVite(); // linkTsconfigPaths: true (default)
import App from "@/App";
import Button from "@components/Button";
import { foo } from "@lib";

Both tsc and Vite resolve the imports. No second plugin needed.

Reading the loaded env from app code

vite.config.ts
import mongezVite from "@mongez/vite";
export default defineConfig({
plugins: [mongezVite()],
});
// src/config.ts — runs at build / dev time
import { env } from "@mongez/dotenv";
// loadEnv was already called from inside the plugin's config hook, so the
// store is populated by the time vite reaches user code.
export const config = {
appName: env("APP_NAME") as string,
port: env("APP_PORT", 3000) as number,
debug: env("DEBUG", false) as boolean,
};

Note: this is for code that runs at build/dev time (e.g. inside vite.config.ts’s own imports or pre-bundling plugins). Code that ships to the browser cannot read from @mongez/dotenv’s store at runtime — use Vite’s own import.meta.env.VITE_* mechanism for that. To expose a value as import.meta.env.VITE_*, prefix it with VITE_ in your .env file.

Apache deploy script

deploy.sh
#!/bin/bash
set -e
STAGE=${1:-production}
# Build with the matching .env file
STAGE=$STAGE vite build
# Ship the zip — compressBuild awaits the archive pipeline inside writeBundle,
# so by the time `vite build` exits, dist/build.zip is finalised on disk.
scp dist/build.zip server:/var/www/$STAGE-staging.zip
ssh server "cd /var/www && unzip -o $STAGE-staging.zip -d $STAGE"

If you’d rather zip the output dir yourself (different archive format, additional files, etc.), disable compressBuild:

mongezVite({ compressBuild: false });
Terminal window
vite build && cd dist && zip -r build.zip ./* && scp build.zip server:...