vite
Vite is a frontend build tool with two halves: a dev server that serves your source over native ES modules, so it starts near-instantly and hot-swaps single modules instead of rebundling, and a production build command that bundles with Rolldown into optimized static assets. You configure it once in vite.config.ts, framework templates (React, Vue, Svelte, Solid, and more) come from create-vite, and a plugin API extends both halves. It is the default build layer for most frontend work that is not tied to a full-stack framework.
The default choice for new frontend builds, and a safe one: fast, well documented, and actively developed with a huge ecosystem. Just treat majors as migrations, not version bumps, since v8 changed the production bundler.
Use it if
- You are starting a SPA or client-rendered app in React, Vue, Svelte, or Solid and want dev-server startup and HMR measured in milliseconds
- You are migrating off webpack and want a config measured in tens of lines instead of hundreds
- You ship a JS/TS library and want library mode to emit ESM and CJS builds without hand-rolling a bundler setup
- You test with Vitest and want one shared config and transform pipeline for app and tests
- You need a full-stack React framework with SSR, routing, and data fetching built in; Next.js or a Vite-based meta-framework (SvelteKit, React Router framework mode, Astro) gives you that, plain Vite does not
- You maintain a legacy webpack build that depends on module federation or deep custom loaders; porting those to Vite plugins is real migration work, not a config swap
- You must support very old browsers; dev mode requires native ESM and legacy production output means adding and tuning @vitejs/plugin-legacy
- Your build relies on Rollup plugin internals: Vite 8 replaced the production bundler with Rolldown, and while most Rollup plugins work, each one needs verifying before you upgrade
Setup reality
npm create vite@latest scaffolds a working app in under a minute and the dev server just works. The friction shows up later: Node 20.19+ or 22.12+ is required, CSS preprocessors (sass, less, stylus) are peer deps you install yourself, env vars silently come back undefined unless prefixed VITE_, and dev (unbundled ESM) and build (Rolldown bundle) are different pipelines, so some bugs only reproduce in vite build. Treat the yearly major as a real upgrade: v8 swapped Rollup for Rolldown under the hood.
Patterns
Scaffold a new appscaffold-project
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install && npm run devThe extra -- is required with npm so the template flag actually reaches create-vite.
Minimal vite.config.tsbase-config
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: { port: 3000, open: true },
})defineConfig exists only for IntelliSense; the plain object is the entire config.
Use environment variablesenv-variables
# .env.local
VITE_API_URL=https://api.example.com
// in client code
const url = import.meta.env.VITE_API_URLVars without the VITE_ prefix are never exposed to the client, and import.meta.env is replaced statically at build time.
Proxy API calls in devdev-proxy
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
})The proxy only runs in the dev server; production needs a real reverse proxy or CORS setup.
Add an @ import aliaspath-alias
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
},
})TypeScript does not read Vite aliases; mirror them in tsconfig paths or editor imports break.
Import many files at onceglob-import
const modules = import.meta.glob('./pages/*.tsx')
for (const path in modules) {
modules[path]().then((mod) => console.log(path, mod))
}Lazy by default; pass { eager: true } to inline the imports at build time instead of returning loader functions.
Reference static assetsstatic-assets
import logoUrl from './assets/logo.svg'
// force a URL for any file type
import workletUrl from './worklet.js?url'Imported assets get content-hashed filenames; files in public/ are served as-is at / and skip hashing.
Build a library instead of an applibrary-mode
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'MyLib',
fileName: 'my-lib',
},
rollupOptions: { external: ['react', 'react-dom'] },
},
})Externalize peer deps yourself or React ends up bundled inside your library.
Build a multi-page appmulti-page-build
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin/index.html'),
},
},
},
})Each page needs its own .html entry; dev-server URLs follow the same directory layout.
Vary config by command or modeconditional-config
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
base: command === 'build' ? '/app/' : '/',
define: { __API__: JSON.stringify(env.API_URL) },
}
})process.env is not populated from .env files inside the config file; use loadEnv.
Handle HMR updates manuallyhmr-accept
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
if (newModule) render(newModule.default)
})
}Always guard with import.meta.hot so the block is dead-code-eliminated from production builds.
Check the production build locallypreview-build
npm run build
npm run previewvite preview serves dist/ locally for smoke testing; it is not a production server.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| webpack | npm | You depend on module federation or a mature loader ecosystem with no Vite equivalent |
| esbuild | npm | You want a raw, scriptable bundler for a library or server code and no dev server |
| @rspack/core | npm | You want webpack API compatibility with Rust-level build speed |