Vite — Build Tool
Vite (French for "fast") is a modern build tool created by Evan You that leverages native ES modules in the browser during development and bundles with Rollup for production. It provides instant server start, lightning-fast HMR, and an optimized production build out of the box.
Unlike traditional bundlers that must build the entire application graph before serving, Vite serves code on-demand via native ESM, only transforming files as the browser requests them. This architecture eliminates the cold-start problem inherent in Webpack and other legacy tools.
Vite's development server starts almost instantly regardless of application size. It uses esbuild for pre-bundling dependencies and serves source files as native ESM.
| 1 | # Create a new Vite project |
| 2 | npm create vite@latest my-app -- --template react-ts |
| 3 | cd my-app |
| 4 | npm install |
| 5 | npm run dev |
| 6 | |
| 7 | # Vite dev server starts in ~200ms |
| 8 | # Output: |
| 9 | # VITE v6.x ready in 187ms |
| 10 | # ➜ Local: http://localhost:5173/ |
| 11 | # ➜ Network: http://192.168.1.5:5173/ |
| Feature | Vite | Webpack (CRA) |
|---|---|---|
| Cold Start | ~200ms | ~5-30s |
| HMR Updates | <50ms | ~200ms-2s |
| HMR Granularity | Module-level (ESM) | Bundle-level |
| Pre-bundling | esbuild (Go) | Terser/Uglify (JS) |
info
Vite provides official templates for all major frameworks and can scaffold a fully configured project in seconds.
| 1 | # Official templates |
| 2 | npm create vite@latest my-app -- --template vanilla-ts |
| 3 | npm create vite@latest my-app -- --template react |
| 4 | npm create vite@latest my-app -- --template react-ts |
| 5 | npm create vite@latest my-app -- --template vue |
| 6 | npm create vite@latest my-app -- --template vue-ts |
| 7 | npm create vite@latest my-app -- --template svelte |
| 8 | npm create vite@latest my-app -- --template svelte-ts |
| 9 | npm create vite@latest my-app -- --template lit |
| 10 | npm create vite@latest my-app -- --template preact-ts |
| 11 | npm create vite@latest my-app -- --template solid |
| 12 | |
| 13 | # Community templates (via create-vite-extra) |
| 14 | npm create vite@latest my-app -- --template ssr-react |
| 15 | npm create vite@latest my-app -- --template lib |
| 16 | npm create vite@latest my-app -- --template electron |
The generated project structure is minimal and intentionally designed:
| 1 | my-app/ |
| 2 | ├── index.html # Entry HTML (not in /public!) |
| 3 | ├── vite.config.ts # Vite configuration |
| 4 | ├── tsconfig.json # TypeScript config |
| 5 | ├── tsconfig.node.json # Node-specific TS config |
| 6 | ├── package.json |
| 7 | ├── public/ # Static assets (served at root) |
| 8 | │ └── favicon.svg |
| 9 | └── src/ |
| 10 | ├── main.tsx # Application entry |
| 11 | ├── App.tsx # Root component |
| 12 | ├── App.css |
| 13 | ├── vite-env.d.ts # Vite type declarations |
| 14 | └── assets/ # Imported assets (processed by Vite) |
| 15 | └── react.svg |
best practice
Vite configuration is expressed through vite.config.ts. The API is intuitive, well-typed, and extensible via plugins.
| 1 | import { defineConfig } from "vite"; |
| 2 | import react from "@vitejs/plugin-react"; |
| 3 | import tailwindcss from "@tailwindcss/vite"; |
| 4 | import path from "path"; |
| 5 | |
| 6 | export default defineConfig({ |
| 7 | plugins: [ |
| 8 | react(), |
| 9 | tailwindcss(), |
| 10 | ], |
| 11 | |
| 12 | resolve: { |
| 13 | alias: { |
| 14 | "@": path.resolve(__dirname, "./src"), |
| 15 | "@components": path.resolve(__dirname, "./src/components"), |
| 16 | "@utils": path.resolve(__dirname, "./src/utils"), |
| 17 | }, |
| 18 | }, |
| 19 | |
| 20 | server: { |
| 21 | port: 3000, |
| 22 | open: true, |
| 23 | proxy: { |
| 24 | "/api": { |
| 25 | target: "http://localhost:8080", |
| 26 | changeOrigin: true, |
| 27 | rewrite: (path) => path.replace(/^\/api/, ""), |
| 28 | }, |
| 29 | }, |
| 30 | }, |
| 31 | |
| 32 | build: { |
| 33 | outDir: "dist", |
| 34 | sourcemap: true, |
| 35 | minify: "esbuild", |
| 36 | rollupOptions: { |
| 37 | output: { |
| 38 | manualChunks: { |
| 39 | vendor: ["react", "react-dom"], |
| 40 | ui: ["@radix-ui/react-dialog", "@radix-ui/react-dropdown-menu"], |
| 41 | }, |
| 42 | }, |
| 43 | }, |
| 44 | }, |
| 45 | |
| 46 | test: { |
| 47 | globals: true, |
| 48 | environment: "jsdom", |
| 49 | setupFiles: "./src/test/setup.ts", |
| 50 | css: true, |
| 51 | }, |
| 52 | }); |
| Option | Purpose | Default |
|---|---|---|
| root | Project root directory | process.cwd() |
| base | Public base path | / |
| build.outDir | Output directory | dist |
| build.target | Browser target | modules |
| build.cssCodeSplit | Split CSS per chunk | true |
| server.hmr | HMR config |
Vite's plugin system is compatible with Rollup plugins and extends them with Vite-specific hooks. The ecosystem is rich and growing rapidly.
| Plugin | Purpose | Official |
|---|---|---|
| @vitejs/plugin-react | React Fast Refresh + JSX transform | ✓ |
| @vitejs/plugin-vue | Single-File Component support | ✓ |
| @vitejs/plugin-legacy | Legacy browser support via Terser | ✓ |
| vite-plugin-pwa | PWA / Service Worker support | — |
| vite-plugin-svgr | SVG as React component | — |
| vite-tsconfig-paths | Auto-resolve tsconfig paths | — |
| @tailwindcss/vite | Tailwind CSS v4 integration | — |
pro tip
Vite uses import.meta.env for environment variables. Variables are exposed through .env files and must be prefixed with VITE_ to be client-accessible.
| 1 | # .env — loaded in all modes |
| 2 | VITE_APP_TITLE=My App |
| 3 | |
| 4 | # .env.development — loaded in dev mode |
| 5 | VITE_API_URL=http://localhost:8080 |
| 6 | |
| 7 | # .env.production — loaded in production |
| 8 | VITE_API_URL=https://api.example.com |
| 9 | |
| 10 | # .env.local — loaded in all modes, gitignored |
| 11 | VITE_STRIPE_KEY=pk_test_... |
| 12 | |
| 13 | # Mode-specific local (highest priority) |
| 14 | # .env.development.local |
| 15 | # .env.production.local |
| 1 | // Usage in application code |
| 2 | const title = import.meta.env.VITE_APP_TITLE; |
| 3 | const apiUrl = import.meta.env.VITE_API_URL; |
| 4 | |
| 5 | // Type safety via IntrinsicAttributes |
| 6 | interface ImportMetaEnv { |
| 7 | readonly VITE_APP_TITLE: string; |
| 8 | readonly VITE_API_URL: string; |
| 9 | readonly VITE_STRIPE_KEY: string; |
| 10 | } |
| 11 | |
| 12 | interface ImportMeta { |
| 13 | readonly env: ImportMetaEnv; |
| 14 | } |
| 15 | |
| 16 | // Built-in env variables |
| 17 | console.log(import.meta.env.MODE); // "development" | "production" |
| 18 | console.log(import.meta.env.BASE_URL); // "/" |
| 19 | console.log(import.meta.env.PROD); // true | false |
| 20 | console.log(import.meta.env.DEV); // true | false |
| 21 | console.log(import.meta.env.SSR); // true | false |
warning
Vite provides first-class SSR support with a low-level API that powers frameworks like Nuxt 3, SvelteKit, and Remix. You can also implement custom SSR without a framework.
| 1 | // server.js — Express-based SSR with Vite |
| 2 | import express from "express"; |
| 3 | import { createServer as createViteServer } from "vite"; |
| 4 | |
| 5 | const app = express(); |
| 6 | const vite = await createViteServer({ |
| 7 | server: { middlewareMode: true }, |
| 8 | appType: "custom", |
| 9 | }); |
| 10 | |
| 11 | app.use(vite.middlewares); |
| 12 | |
| 13 | app.use("*", async (req, res) => { |
| 14 | const url = req.originalUrl; |
| 15 | |
| 16 | try { |
| 17 | const template = await vite.transformIndexHtml( |
| 18 | url, |
| 19 | readFileSync("index.html", "utf-8") |
| 20 | ); |
| 21 | const render = (await vite.ssrLoadModule("/src/entry-server.tsx")).render; |
| 22 | const appHtml = await render(url); |
| 23 | const html = template.replace("<!--ssr-outlet-->", appHtml); |
| 24 | res.status(200).set({ "Content-Type": "text/html" }).end(html); |
| 25 | } catch (e) { |
| 26 | vite.ssrFixStacktrace(e); |
| 27 | res.status(500).end(e.message); |
| 28 | } |
| 29 | }); |
| 30 | |
| 31 | app.listen(5173); |
best practice
Vite offers first-class integration with all major frameworks through official plugins. Here is how to configure each:
React + TypeScript
| 1 | import { defineConfig } from "vite"; |
| 2 | import react from "@vitejs/plugin-react"; |
| 3 | |
| 4 | export default defineConfig({ |
| 5 | plugins: [ |
| 6 | react({ |
| 7 | babel: { |
| 8 | plugins: ["babel-plugin-macros"], |
| 9 | }, |
| 10 | fastRefresh: true, |
| 11 | }), |
| 12 | ], |
| 13 | }); |
Vue 3
| 1 | import { defineConfig } from "vite"; |
| 2 | import vue from "@vitejs/plugin-vue"; |
| 3 | import vueJsx from "@vitejs/plugin-vue-jsx"; |
| 4 | |
| 5 | export default defineConfig({ |
| 6 | plugins: [ |
| 7 | vue({ |
| 8 | reactivityTransform: true, |
| 9 | }), |
| 10 | vueJsx(), |
| 11 | ], |
| 12 | }); |
Svelte
| 1 | import { defineConfig } from "vite"; |
| 2 | import { svelte } from "@sveltejs/vite-plugin-svelte"; |
| 3 | |
| 4 | export default defineConfig({ |
| 5 | plugins: [ |
| 6 | svelte({ |
| 7 | hot: true, |
| 8 | }), |
| 9 | ], |
| 10 | }); |
The dev server proxy forwards API requests to a backend server, avoiding CORS issues during development. It supports WebSocket proxying, path rewriting, and complex routing.
| 1 | import { defineConfig } from "vite"; |
| 2 | |
| 3 | export default defineConfig({ |
| 4 | server: { |
| 5 | proxy: { |
| 6 | // Simple proxy |
| 7 | "/api": "http://localhost:8080", |
| 8 | |
| 9 | // With path rewriting |
| 10 | "/api/v2": { |
| 11 | target: "http://localhost:8080", |
| 12 | changeOrigin: true, |
| 13 | rewrite: (path) => path.replace(/^\/api\/v2/, "/v2"), |
| 14 | }, |
| 15 | |
| 16 | // WebSocket proxy |
| 17 | "/ws": { |
| 18 | target: "ws://localhost:8080", |
| 19 | ws: true, |
| 20 | }, |
| 21 | |
| 22 | // Multiple paths to same target |
| 23 | "^/(api|auth|graphql)/.*": { |
| 24 | target: "http://localhost:4000", |
| 25 | changeOrigin: true, |
| 26 | }, |
| 27 | |
| 28 | // With headers and cookies |
| 29 | "/api/secure": { |
| 30 | target: "https://api.example.com", |
| 31 | secure: false, |
| 32 | headers: { |
| 33 | "X-Custom-Header": "value", |
| 34 | }, |
| 35 | cookieDomainRewrite: ".example.com", |
| 36 | }, |
| 37 | }, |
| 38 | }, |
| 39 | }); |
info
Vite uses Rollup under the hood for production builds, producing optimized, tree-shaken bundles with automatic code splitting.
| 1 | # Build for production |
| 2 | npm run build |
| 3 | |
| 4 | # Output structure: |
| 5 | dist/ |
| 6 | ├── index.html |
| 7 | ├── assets/ |
| 8 | │ ├── index-Bzcxv9q0.js # Main entry chunk (hashed) |
| 9 | │ ├── vendor-Bk9s8a2p.js # Vendor chunk (react, react-dom) |
| 10 | │ ├── ui-Df7v3mN1.js # UI library chunk |
| 11 | │ ├── index-Cw4sNf2M.css # Extracted CSS (hashed) |
| 12 | │ └── lazy-Chunk-Hn8vJk2.js # Lazy-loaded chunk |
| 13 | ├── favicon.svg |
| 14 | └── 404.html |
| 15 | |
| 16 | # Preview production build locally |
| 17 | npm run preview |
| 1 | import { defineConfig } from "vite"; |
| 2 | |
| 3 | export default defineConfig({ |
| 4 | build: { |
| 5 | target: "es2020", // Transpile target |
| 6 | outDir: "dist", |
| 7 | assetsDir: "assets", |
| 8 | sourcemap: false, // Disable in production |
| 9 | minify: "esbuild", // "esbuild" (fast) or "terser" (smaller) |
| 10 | cssMinify: "lightningcss", // Lightning CSS for faster CSS minification |
| 11 | cssCodeSplit: true, // Extract CSS per chunk |
| 12 | reportCompressedSize: true, // Show gzip sizes in build output |
| 13 | chunkSizeWarningLimit: 500, // Warn if chunk > 500KB |
| 14 | |
| 15 | rollupOptions: { |
| 16 | input: { // Multi-page app support |
| 17 | main: "index.html", |
| 18 | admin: "admin/index.html", |
| 19 | }, |
| 20 | output: { |
| 21 | manualChunks(id) { |
| 22 | if (id.includes("node_modules")) { |
| 23 | if (id.includes("react")) return "react-vendor"; |
| 24 | if (id.includes("lodash")) return "lodash"; |
| 25 | return "vendor"; |
| 26 | } |
| 27 | }, |
| 28 | }, |
| 29 | }, |
| 30 | }, |
| 31 | }); |
best practice
| Dimension | Vite | Webpack |
|---|---|---|
| Dev Server Strategy | Native ESM (on-demand) | Bundle everything |
| Cold Start | <500ms | 5-60s |
| HMR | Module-level, instant | Bundle-level, degrades |
| Prod Bundler | Rollup (configurable) | Webpack (own) |
| Pre-bundling | esbuild (Go) | JS-based |
| Config Complexity | Minimal (sensible defaults) | High (verbose config) |
| Plugin Ecosystem | Growing (Rollup-compatible) | Mature (5000+) |
| Legacy Browser | Via @vitejs/plugin-legacy | Built-in (target config) |
note