|$ curl https://forge-ai.dev/api/markdown?path=docs/build-tools/vite
$cat docs/vite-—-build-tool.md
updated Recently·35 min read·published

Vite — Build Tool

Build ToolsViteBeginner to Advanced
Introduction

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.

Dev Server & HMR

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.

terminal
Bash
1# Create a new Vite project
2npm create vite@latest my-app -- --template react-ts
3cd my-app
4npm install
5npm 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/
FeatureViteWebpack (CRA)
Cold Start~200ms~5-30s
HMR Updates<50ms~200ms-2s
HMR GranularityModule-level (ESM)Bundle-level
Pre-bundlingesbuild (Go)Terser/Uglify (JS)

info

Vite's HMR stays fast regardless of project size because it only invalidates the exact module chain affected by a change, not the entire bundle. This makes it ideal for large monorepo projects.
Project Scaffolding & Templates

Vite provides official templates for all major frameworks and can scaffold a fully configured project in seconds.

terminal
Bash
1# Official templates
2npm create vite@latest my-app -- --template vanilla-ts
3npm create vite@latest my-app -- --template react
4npm create vite@latest my-app -- --template react-ts
5npm create vite@latest my-app -- --template vue
6npm create vite@latest my-app -- --template vue-ts
7npm create vite@latest my-app -- --template svelte
8npm create vite@latest my-app -- --template svelte-ts
9npm create vite@latest my-app -- --template lit
10npm create vite@latest my-app -- --template preact-ts
11npm create vite@latest my-app -- --template solid
12
13# Community templates (via create-vite-extra)
14npm create vite@latest my-app -- --template ssr-react
15npm create vite@latest my-app -- --template lib
16npm create vite@latest my-app -- --template electron

The generated project structure is minimal and intentionally designed:

project-tree
Bash
1my-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

Unlike Webpack, Vite uses index.htmlas the entry point — not a JS file. The HTML file is the root of the dependency graph. This aligns with the browser's native behavior and is more intuitive.
Configuration

Vite configuration is expressed through vite.config.ts. The API is intuitive, well-typed, and extensible via plugins.

vite.config.ts
TypeScript
1import { defineConfig } from "vite";
2import react from "@vitejs/plugin-react";
3import tailwindcss from "@tailwindcss/vite";
4import path from "path";
5
6export 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});
OptionPurposeDefault
rootProject root directoryprocess.cwd()
basePublic base path/
build.outDirOutput directorydist
build.targetBrowser targetmodules
build.cssCodeSplitSplit CSS per chunktrue
server.hmrHMR config
Plugin Ecosystem

Vite's plugin system is compatible with Rollup plugins and extends them with Vite-specific hooks. The ecosystem is rich and growing rapidly.

PluginPurposeOfficial
@vitejs/plugin-reactReact Fast Refresh + JSX transform
@vitejs/plugin-vueSingle-File Component support
@vitejs/plugin-legacyLegacy browser support via Terser
vite-plugin-pwaPWA / Service Worker support
vite-plugin-svgrSVG as React component
vite-tsconfig-pathsAuto-resolve tsconfig paths
@tailwindcss/viteTailwind CSS v4 integration
🔥

pro tip

You can use any Rollup plugin in Vite with no additional configuration. Plugins like rollup-plugin-visualizer for bundle analysis work out of the box.
Environment Variables & Modes

Vite uses import.meta.env for environment variables. Variables are exposed through .env files and must be prefixed with VITE_ to be client-accessible.

.env
Bash
1# .env — loaded in all modes
2VITE_APP_TITLE=My App
3
4# .env.development — loaded in dev mode
5VITE_API_URL=http://localhost:8080
6
7# .env.production — loaded in production
8VITE_API_URL=https://api.example.com
9
10# .env.local — loaded in all modes, gitignored
11VITE_STRIPE_KEY=pk_test_...
12
13# Mode-specific local (highest priority)
14# .env.development.local
15# .env.production.local
env-usage.ts
TypeScript
1// Usage in application code
2const title = import.meta.env.VITE_APP_TITLE;
3const apiUrl = import.meta.env.VITE_API_URL;
4
5// Type safety via IntrinsicAttributes
6interface ImportMetaEnv {
7 readonly VITE_APP_TITLE: string;
8 readonly VITE_API_URL: string;
9 readonly VITE_STRIPE_KEY: string;
10}
11
12interface ImportMeta {
13 readonly env: ImportMetaEnv;
14}
15
16// Built-in env variables
17console.log(import.meta.env.MODE); // "development" | "production"
18console.log(import.meta.env.BASE_URL); // "/"
19console.log(import.meta.env.PROD); // true | false
20console.log(import.meta.env.DEV); // true | false
21console.log(import.meta.env.SSR); // true | false

warning

Only variables prefixed with VITE_ are exposed to client code. Server-side variables (e.g., database passwords, API secrets) should never have the VITE_ prefix and will not be leaked to the client.
SSR Mode

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.

server.js
TypeScript
1// server.js — Express-based SSR with Vite
2import express from "express";
3import { createServer as createViteServer } from "vite";
4
5const app = express();
6const vite = await createViteServer({
7 server: { middlewareMode: true },
8 appType: "custom",
9});
10
11app.use(vite.middlewares);
12
13app.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
31app.listen(5173);

best practice

For production SSR, consider using a framework built on Vite's SSR (like Nuxt or SvelteKit) rather than rolling your own. They handle streaming, caching, error boundaries, and deployment out of the box.
Framework Integration

Vite offers first-class integration with all major frameworks through official plugins. Here is how to configure each:

React + TypeScript

vite.config.ts
TypeScript
1import { defineConfig } from "vite";
2import react from "@vitejs/plugin-react";
3
4export default defineConfig({
5 plugins: [
6 react({
7 babel: {
8 plugins: ["babel-plugin-macros"],
9 },
10 fastRefresh: true,
11 }),
12 ],
13});

Vue 3

vite.config.ts
TypeScript
1import { defineConfig } from "vite";
2import vue from "@vitejs/plugin-vue";
3import vueJsx from "@vitejs/plugin-vue-jsx";
4
5export default defineConfig({
6 plugins: [
7 vue({
8 reactivityTransform: true,
9 }),
10 vueJsx(),
11 ],
12});

Svelte

vite.config.ts
TypeScript
1import { defineConfig } from "vite";
2import { svelte } from "@sveltejs/vite-plugin-svelte";
3
4export default defineConfig({
5 plugins: [
6 svelte({
7 hot: true,
8 }),
9 ],
10});
Proxy Configuration

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.

vite.config.ts
TypeScript
1import { defineConfig } from "vite";
2
3export 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

Use changeOrigin: true when proxying to virtual hosted servers (most cloud APIs). This changes the Host header to match the target, preventing 403 errors.
Production Build

Vite uses Rollup under the hood for production builds, producing optimized, tree-shaken bundles with automatic code splitting.

terminal
Bash
1# Build for production
2npm run build
3
4# Output structure:
5dist/
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
17npm run preview
vite.config.ts
TypeScript
1import { defineConfig } from "vite";
2
3export 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

For maximum production performance, set build.target to "es2020" (or higher) if you do not need to support legacy browsers. This generates smaller, faster code by leveraging modern syntax like optional chaining and nullish coalescing natively.
Vite vs. Webpack
DimensionViteWebpack
Dev Server StrategyNative ESM (on-demand)Bundle everything
Cold Start<500ms5-60s
HMRModule-level, instantBundle-level, degrades
Prod BundlerRollup (configurable)Webpack (own)
Pre-bundlingesbuild (Go)JS-based
Config ComplexityMinimal (sensible defaults)High (verbose config)
Plugin EcosystemGrowing (Rollup-compatible)Mature (5000+)
Legacy BrowserVia @vitejs/plugin-legacyBuilt-in (target config)
📝

note

Vite is not always the right choice. For projects that depend heavily on Webpack-specific plugins (Module Federation, certain loaders), or that need to support IE11 without extra configuration, Webpack may still be preferable.
Best Practices
Always use VITE_ prefix for client-exposed env variables
Use resolve.alias with @ prefix for clean imports — configure tsconfig paths too
Split vendor chunks strategically: react/react-dom, UI libraries, utility libraries
Use CSS modules or CSS-in-JS — avoid global CSS for component styles
Prefer @vitejs/plugin-legacy over manual polyfill configuration
Lazy-load routes with dynamic import() for large applications
Use build.sourcemap: 'hidden' for production (security without losing debug capability)
Pin your Vite version in package.json — breaking changes happen in minor versions
Configure build.target based on your actual browser support matrix
Use the Vite dev console overlay — it shows errors inline with source mapping
$Blueprint — Engineering Documentation·Section ID: BT-VITE-01·Revision: 1.0