Vite Patterns
Environment Variables
- Only
VITE_ prefixed vars are exposed to client code — DB_PASSWORD stays server-side, VITE_API_URL is bundled
- Access via
import.meta.env.VITE_* not process.env — process.env is Node-only and undefined in browser
.env.local overrides .env and is gitignored by default — use for local secrets
import.meta.env.MODE is development or production — use for conditional logic, not NODE_ENV
CommonJS Compatibility
- Pure ESM by default — CommonJS packages need
optimizeDeps.include for pre-bundling
require() doesn't work in Vite — use import or createRequire from module for dynamic requires
- Some packages ship broken ESM — add to
ssr.noExternal or optimizeDeps.exclude and let Vite transform them
- Named exports from CommonJS may fail — use default import and destructure:
import pkg from 'pkg'; const { method } = pkg
Dependency Pre-bundling
- Vite pre-bundles dependencies on first run — delete
node_modules/.vite to force rebuild after package changes
- Large dependencies slow down dev server start — add rarely-changing ones to
optimizeDeps.include for persistent cache
- Linked local packages (
npm link) aren't pre-bundled — add to optimizeDeps.include explicitly
optimizeDeps.force: true rebuilds every time — only for debugging, kills dev performance
Path Aliases
- Configure in both
vite.config.ts AND tsconfig.json — Vite uses its own, TypeScript uses tsconfig
- Use
path.resolve(__dirname, './src') not relative paths — relative breaks depending on working directory
@/ alias is not built-in — must configure manually unlike some frameworks
// vite.config.ts
resolve: {
alias: { '@': path.resolve(__dirname, './src') }
}
Dev Server Proxy
- Proxy only works in dev — production needs actual CORS config or reverse proxy
changeOrigin: true rewrites Host header — required for most APIs that check origin
- WebSocket proxy needs explicit
ws: true — HTTP proxy doesn't forward WS by default
- Trailing slashes matter:
/api proxies /api/users, /api/ only proxies /api//users
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
Static Assets
public/ files served at root, not processed — use for favicons, robots.txt, files that need exact paths
src/assets/ files are processed, hashed, can be imported — use for images, fonts referenced in code
- Import assets to get resolved URL:
import logo from './logo.png' — hardcoded paths break after build
new URL('./img.png', import.meta.url) for dynamic paths — template literals with variables don't work
Build Optimization
build.rollupOptions.output.manualChunks for code splitting — without it, one giant bundle
- Analyze bundle with
rollup-plugin-visualizer — find unexpected large dependencies
build.target defaults to modern browsers — set 'es2015' for legacy support, but increases bundle size
build.cssCodeSplit: true (default) — each async chunk gets its own CSS file
Library Mode
build.lib for npm packages — different config from app mode
- Set
external for peer dependencies — don't bundle React/Vue into your library
- Generate types separately with
tsc — Vite doesn't emit .d.ts files
- Both ESM and CJS outputs:
formats: ['es', 'cjs'] — some consumers still need require()
HMR Issues
- Circular imports break HMR — refactor to break the cycle or full reload triggers
- State lost on HMR means component isn't accepting updates — check for
import.meta.hot.accept()
- CSS changes trigger full reload if imported in JS that doesn't accept HMR — import CSS in components that do
server.hmr.overlay: false hides error overlay — useful for custom error handling but hides issues
SSR Configuration
ssr.external for Node-only packages — prevents bundling node_modules in SSR build
ssr.noExternal forces bundling — needed for packages with browser-specific imports
- CSS imports fail in SSR by default — use
?inline suffix or configure css.postcss for SSR
1---2name: vite3description: Configure and optimize Vite for development, production builds, and library bundling.4---5
6# Vite Patterns
7
8## Environment Variables
9- Only `VITE_` prefixed vars are exposed to client code — `DB_PASSWORD` stays server-side, `VITE_API_URL` is bundled
10- Access via `import.meta.env.VITE_*` not `process.env` — process.env is Node-only and undefined in browser
11- `.env.local` overrides `.env` and is gitignored by default — use for local secrets
12- `import.meta.env.MODE` is `development` or `production` — use for conditional logic, not `NODE_ENV`
13
14## CommonJS Compatibility
15- Pure ESM by default — CommonJS packages need `optimizeDeps.include` for pre-bundling
16- `require()` doesn't work in Vite — use `import` or `createRequire` from `module` for dynamic requires
17- Some packages ship broken ESM — add to `ssr.noExternal` or `optimizeDeps.exclude` and let Vite transform them
18- Named exports from CommonJS may fail — use default import and destructure: `import pkg from 'pkg'; const { method } = pkg`
19
20## Dependency Pre-bundling
21- Vite pre-bundles dependencies on first run — delete `node_modules/.vite` to force rebuild after package changes
22- Large dependencies slow down dev server start — add rarely-changing ones to `optimizeDeps.include` for persistent cache
23- Linked local packages (`npm link`) aren't pre-bundled — add to `optimizeDeps.include` explicitly
24- `optimizeDeps.force: true` rebuilds every time — only for debugging, kills dev performance
25
26## Path Aliases
27- Configure in both `vite.config.ts` AND `tsconfig.json` — Vite uses its own, TypeScript uses tsconfig
28- Use `path.resolve(__dirname, './src')` not relative paths — relative breaks depending on working directory
29- `@/` alias is not built-in — must configure manually unlike some frameworks
30
31```typescript
32// vite.config.ts
33resolve: {
34 alias: { '@': path.resolve(__dirname, './src') }
35}
36```
37
38## Dev Server Proxy
39- Proxy only works in dev — production needs actual CORS config or reverse proxy
40- `changeOrigin: true` rewrites Host header — required for most APIs that check origin
41- WebSocket proxy needs explicit `ws: true` — HTTP proxy doesn't forward WS by default
42- Trailing slashes matter: `/api` proxies `/api/users`, `/api/` only proxies `/api//users`
43
44```typescript
45server: {
46 proxy: {
47 '/api': {
48 target: 'http://localhost:3000',
49 changeOrigin: true,
50 rewrite: path => path.replace(/^\/api/, '')
51 }
52 }
53}
54```
55
56## Static Assets
57- `public/` files served at root, not processed — use for favicons, robots.txt, files that need exact paths
58- `src/assets/` files are processed, hashed, can be imported — use for images, fonts referenced in code
59- Import assets to get resolved URL: `import logo from './logo.png'` — hardcoded paths break after build
60- `new URL('./img.png', import.meta.url)` for dynamic paths — template literals with variables don't work
61
62## Build Optimization
63- `build.rollupOptions.output.manualChunks` for code splitting — without it, one giant bundle
64- Analyze bundle with `rollup-plugin-visualizer` — find unexpected large dependencies
65- `build.target` defaults to modern browsers — set `'es2015'` for legacy support, but increases bundle size
66- `build.cssCodeSplit: true` (default) — each async chunk gets its own CSS file
67
68## Library Mode
69- `build.lib` for npm packages — different config from app mode
70- Set `external` for peer dependencies — don't bundle React/Vue into your library
71- Generate types separately with `tsc` — Vite doesn't emit `.d.ts` files
72- Both ESM and CJS outputs: `formats: ['es', 'cjs']` — some consumers still need require()
73
74## HMR Issues
75- Circular imports break HMR — refactor to break the cycle or full reload triggers
76- State lost on HMR means component isn't accepting updates — check for `import.meta.hot.accept()`
77- CSS changes trigger full reload if imported in JS that doesn't accept HMR — import CSS in components that do
78- `server.hmr.overlay: false` hides error overlay — useful for custom error handling but hides issues
79
80## SSR Configuration
81- `ssr.external` for Node-only packages — prevents bundling node_modules in SSR build
82- `ssr.noExternal` forces bundling — needed for packages with browser-specific imports
83- CSS imports fail in SSR by default — use `?inline` suffix or configure `css.postcss` for SSR