cheat sheet
vite
Package-level reference for vite on npm — install variants, scaffold templates, plugin ecosystem under @vitejs/plugin-*, framework integrations, and alternatives.
vite
What it is
vite is a frontend build tool created by Evan You (Vue.js author) and now maintained under the VoidZero / vitejs org. In dev it serves source files as native ES modules — the browser pulls modules on demand, so server startup is near-instant regardless of project size. For production it bundles via Rollup (and increasingly via Rolldown, the in-house Rust port) with automatic code splitting, tree-shaking, and asset hashing.
It's the default build tool for Vue, SvelteKit (under the hood), Astro, Remix's new compiler, and most modern React app templates. The package itself is small — most of the surface area lives in the @vitejs/plugin-* and community plugin ecosystem.
Install
# Scaffold a new project (interactive)
npm create vite@latest
# Non-interactive with a template
npm create vite@latest my-app -- --template react-ts
npm create vite@latest my-app -- --template vue-ts
npm create vite@latest my-app -- --template svelte-ts
npm create vite@latest my-app -- --template vanilla-ts
Output: scaffolds my-app/ with package.json, vite.config.ts, and a starter source tree.
# Add vite to an existing project (devDep)
npm install -D vite
pnpm add -D vite
yarn add -D vite
bun add -d vite
Output: vite binary on PATH under node_modules/.bin/vite; ready to run vite, vite build, vite preview.
# Deno (npm specifier)
deno add npm:vite
Output: entry added to deno.json import map.
Versioning & Node support
- Current major line is
7.x(released late 2024 — the line that landed Environment API stabilisation and the start of the Rolldown migration).6.xis in maintenance. - Vite drops Node EOL versions on each major bump.
7.xrequires Node 20.19+ or 22.12+;6.xaccepted Node 18. - ESM-only since
5.x—vite.config.js(CJS) still works because Vite transpiles the config on the fly, but Vite itself cannot berequire()'d. - Always a dev dependency —
vite buildemits a staticdist/that ships without vite at runtime. - SemVer mostly honoured, but minor releases can break exotic plugin internals (Rollup version bumps, esbuild API tweaks). Pin plugin versions alongside vite.
Package metadata
- Maintainer: VoidZero (Evan You's company) + vitejs core team
- Project home: github.com/vitejs/vite
- Docs: vite.dev
- npm: npmjs.com/package/vite
- License: MIT
- First released: 2020
- Downloads: tens of millions per week — consistently top-20 on npm
Peer dependencies & extras
vite itself has minimal peers — rollup and esbuild are bundled. Functionality comes from plugins:
| Plugin | Purpose |
|---|---|
@vitejs/plugin-react | React Fast Refresh + JSX transform. |
@vitejs/plugin-react-swc | Same, but uses SWC instead of Babel — faster. |
@vitejs/plugin-vue | Vue 3 SFC support. |
@vitejs/plugin-vue-jsx | Vue 3 JSX/TSX support. |
@vitejs/plugin-legacy | Polyfill / legacy-browser bundle alongside the modern one. |
@vitejs/plugin-basic-ssl | Self-signed HTTPS for local dev. |
vite-plugin-svelte | Svelte / SvelteKit integration. |
vite-tsconfig-paths | Resolve TS paths aliases. |
vite-plugin-pwa | Service worker / manifest. |
unplugin-* | Cross-bundler plugin runtimes (Vite, Rollup, webpack, esbuild). |
Rollup plugins (@rollup/plugin-*) generally work in Vite as well — Vite's plugin API extends Rollup's.
Alternatives
| Tool | Trade-off |
|---|---|
| webpack | Mature, huge plugin ecosystem. Slower dev startup; complex config. The "old guard" — still widely used in enterprise. |
| rollup | Library-focused bundler. Vite uses it under the hood for prod builds. Pick directly for publishing libraries. |
| esbuild | Extremely fast Go-based bundler. Lower-level than Vite; less ergonomic for SPA dev (no built-in HMR pipeline, less plugin reach). |
| parcel | Zero-config bundler. Friendly for prototypes; smaller ecosystem and slower than Vite for large apps. |
| turbopack | Vercel's Rust bundler, default for Next.js dev (and now stable for prod). Tied closely to Next; not a general drop-in. |
| rspack | ByteDance's Rust port of webpack — drop-in webpack replacement. Different niche from Vite (config-compatible with webpack, not ESM-first). |
| rolldown | The Rust replacement for Rollup, written by the Vite team. Vite is migrating to it under the hood; not normally used directly. |
Common gotchas
import.meta.envvsprocess.env. Vite exposes env vars underimport.meta.env, not Node'sprocess.env. Only variables prefixed withVITE_are exposed to client code — anything else stays server-side, even in a.envfile. Forgetting the prefix produces silentundefinedat runtime.- CommonJS dependency interop. Vite's dev server expects ESM. CJS deps go through
optimizeDepspre-bundling, which sometimes mis-detects named exports. Symptom:import { foo } from "cjs-pkg"works in prod but throws "does not provide an export named 'foo'" in dev. Fix: add the package tooptimizeDeps.includeor use a default import. - HMR boundary surprises. Only modules that explicitly accept HMR (or are imported by one that does) reload in place; the rest trigger a full page reload. Framework plugins (
@vitejs/plugin-react) wire this up for components but not for arbitrary side-effecting modules. baseconfig for non-root deployments. Deploying toexample.com/app/requiresbase: "/app/"invite.config.ts. Forgetting it produces a working dev server and a prod build with broken asset URLs that all 404.- Public dir vs imported assets. Files in
public/are copied as-is and referenced by absolute URL; files imported from source (import logo from "./logo.svg") get hashed and inlined into the build graph. Mixing them up means missing files in prod. - Source maps in libraries. When publishing a library built with
vite build --mode lib, omitsourcemap: trueunless you want to ship.mapfiles to consumers — they bloat the published package and leak source paths. - The
defineoption does string replacement, not eval.define: { __VERSION__: "1.0" }literally substitutes the string1.0in source — usually you wantJSON.stringify("1.0")so the replacement is a valid JS string literal.
Real-world recipes
Concrete worked examples for the package surface — custom plugin authoring, monorepo aliasing, multi-environment builds, and library mode for npm publishing.
Custom plugin — inject a virtual module
Virtual modules are imports that don't exist on disk — useful for injecting compile-time data (build hash, feature flags, generated route manifests). The convention is to prefix the id with virtual: and load it with a leading \0 so Rollup treats it as opaque.
// vite-plugin-build-info.ts
import type { Plugin } from "vite";
export function buildInfo(): Plugin {
const virtualId = "virtual:build-info";
const resolvedId = "\0" + virtualId;
return {
name: "vite-plugin-build-info",
resolveId(id) {
if (id === virtualId) return resolvedId;
},
load(id) {
if (id === resolvedId) {
return `export const buildTime = ${JSON.stringify(new Date().toISOString())};
export const commit = ${JSON.stringify(process.env.GIT_SHA ?? "unknown")};`;
}
},
};
}
Consumed in source code:
import { buildTime, commit } from "virtual:build-info";
console.log(`Built ${commit} at ${buildTime}`);
Monorepo aliases — share a config across workspaces
Hoist vite.config.ts resolution to a workspace root, then have leaf packages extend it via mergeConfig. Aliases pointing at sibling packages keep type-checking and HMR working across the boundary.
// packages/shared/vite.config.shared.ts
import { defineConfig } from "vite";
import path from "node:path";
export const sharedConfig = defineConfig({
resolve: {
alias: {
"@org/ui": path.resolve(__dirname, "../ui/src"),
"@org/api": path.resolve(__dirname, "../api/src"),
},
},
});
// apps/web/vite.config.ts
import { defineConfig, mergeConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { sharedConfig } from "../../packages/shared/vite.config.shared";
export default mergeConfig(sharedConfig, defineConfig({
plugins: [react()],
server: { port: 5173 },
}));
Library mode with subpath exports
When publishing a library, library mode emits multiple formats and lets package.json exports route the right one per consumer.
// vite.config.ts (library)
export default defineConfig({
build: {
lib: {
entry: {
index: "src/index.ts",
utils: "src/utils/index.ts",
},
formats: ["es", "cjs"],
},
rollupOptions: {
external: ["react", "react-dom"],
output: { preserveModules: true, preserveModulesRoot: "src" },
},
},
});
{
"exports": {
".": { "import": "./dist/index.js", "require": "./dist/index.cjs" },
"./utils": { "import": "./dist/utils/index.js", "require": "./dist/utils/index.cjs" }
}
}
Production deployment
Vite's prod output is a static dist/ — but the choices made at build time decide how it behaves under a CDN, edge function, or container.
Asset hashing and cache headers
Hashed filenames (index-Dg8HUZFZ.js) opt into immutable caching. Pair with Cache-Control: public, max-age=31536000, immutable on the static host. index.html itself must NOT be hashed and should be served with Cache-Control: no-cache (revalidate every request) so deploys propagate instantly.
build: {
assetsDir: "assets",
rollupOptions: {
output: {
entryFileNames: "assets/[name]-[hash].js",
chunkFileNames: "assets/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash][extname]",
},
},
}
Code splitting strategy
Default behaviour: one chunk per dynamic import(), one shared vendor chunk per route group. Function-form manualChunks lets you partition by node_modules segment — keep React + its companions together so route changes don't re-download them.
manualChunks(id) {
if (id.includes("/node_modules/react") || id.includes("/node_modules/scheduler")) return "react";
if (id.includes("/node_modules/")) return "vendor";
}
CSP-friendly builds
Vite emits inline <script type="module"> for the entry by default. For strict Content-Security-Policy headers (no unsafe-inline), use a nonce-injecting plugin or accept a hash-based CSP — the vite-plugin-csp-guard and vite-plugin-html ecosystems both cover this.
# Compute Subresource Integrity for assets
npm install -D vite-plugin-sri3
Output:
added 1 package, and audited 274 packages in 2s
Performance tuning
Vite is fast by default — when it isn't, the bottleneck is almost always (a) optimizeDeps re-bundling, (b) a slow plugin transform, or (c) source-map generation on a large bundle.
Pre-bundle deps eagerly
Cold start in dev pauses when an import lands on a CJS package Vite hasn't seen before. Pre-list expensive deps so the first navigation never blocks.
optimizeDeps: {
include: ["lodash-es", "date-fns", "@mui/material"],
exclude: ["@my/local-pkg"],
esbuildOptions: { target: "es2022" },
}
Profile a slow plugin
DEBUG=vite:transform,vite:resolve npx vite build 2>&1 | tee build.log
sort -t' ' -k2 -nr build.log | head -20 # slowest transforms first
Output:
vite:transform 412ms src/legacy/Editor.tsx
vite:transform 287ms src/data/locales/all.ts
vite:transform 184ms node_modules/.vite/deps/three.js
vite:transform 92ms src/components/Charts.tsx
HMR boundary discipline
A module without import.meta.hot.accept() propagates invalidation upward until it finds one — or falls through to a full reload. Framework plugins wire HMR for components; arbitrary side-effect modules (analytics initialisers, polyfills) trigger full reloads. Either accept the reload or wrap the side effect in an HMR-accepting boundary.
if (import.meta.hot) {
import.meta.hot.accept(() => { /* re-run side effect */ });
}
esbuild vs Rollup boundary
Dev uses esbuild for TS/JSX transforms; prod uses Rollup for the final bundle. Plugins that hook only into Rollup (transform, generateBundle) don't run in dev — opt in with apply: "build" if a plugin is build-only, or duplicate the work in the dev path.
ESM/CJS interop & bundling
Vite is ESM-first since v5 — vite.config.js (CJS) still loads because Vite transpiles configs on the fly, but require("vite") from a CJS consumer is no longer supported. Inside the dev server, every module is served as ESM; CJS deps go through optimizeDeps pre-bundling.
// CJS package with named exports — Vite's pre-bundler usually detects them
import { fn } from "some-cjs-lib";
// If detection misses, add to optimizeDeps with explicit include + named exports hint
optimizeDeps: {
include: ["some-cjs-lib"],
needsInterop: ["some-cjs-lib"],
}
The build output respects lib.formats — defaults to ["es", "umd"] for library mode. For dual-publish, use ["es", "cjs"] and pair with the exports map in package.json. Skipping the cjs format and relying on bundler interop usually works for app consumers; pure-Node CJS scripts (no bundler) still need the .cjs artefact.
Version migration guide
Vite has shipped four majors in five years; the breaks cluster in the plugin API and Node floor.
| From → To | Highlights |
|---|---|
| 3 → 4 | esbuild moved to ESM; define no longer auto-stringifies values. Plugins targeting Rollup 2 needed updates for Rollup 3. |
| 4 → 5 | Node 18+ required. CJS Node API removed — require("vite") no longer works; switch to await import("vite"). splitVendorChunkPlugin deprecated; write manualChunks directly. |
| 5 → 6 | Environment API graduates from experimental. build.modulePreload.polyfill defaults to true. Sass legacy API removed — use sass-embedded. |
| 6 → 7 | Node 20.19+ / 22.12+ required. Rolldown rollout begins behind a flag. transformIndexHtml hook order normalised; check custom plugins. |
defineConfig mode signature
// v4: positional command
export default defineConfig(({ command, mode }) => ({ /* ... */ }));
// v5+: same signature, but mode now also receives `isSsrBuild` and `isPreview`
export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => ({ /* ... */ }));
Security considerations
Vite's dev server and prod build have different threat models — the dev server is local-only by default, the prod build is whatever you ship.
import.meta.envleak prevention. OnlyVITE_*vars are inlined. Server secrets (DB URLs, API keys) without the prefix never reach the client bundle — but only at build time. If a server-side render also runs, gated byimport.meta.env.SSR, ensure those vars are not stringified into the client chunk.server.host: truebinds on every interface. On a shared LAN, anyone can hit your dev server and exfiltrate source. Usehost: "127.0.0.1"(default) or set up an explicit allowlist.server.fs.allowrestricts which files the dev server can read. Out of the box, requests outside the project root return 403. If you symlink in a parent monorepo workspace, widenfs.allowexplicitly rather than disabling the check.- CSP for production HTML. Vite's default
<script type="module">inline tag requires'unsafe-inline'unless you nonce-inject. Thevite-plugin-csp-guardplugin computes hashes for every emitted inline script. - Source maps in prod.
sourcemap: trueships maps to the public bucket — useful for debugging, but they leak the entire source tree. Usesourcemap: "hidden"and upload to your error tracker instead.
Troubleshooting common errors
The most-googled Vite error messages and what they actually mean.
[plugin:vite:import-analysis] Failed to resolve import
The dev server's static import analysis couldn't locate a module specifier. Common causes:
- The package isn't installed (run
npm install). - The import path uses a TS-resolution alias that Vite doesn't know about — Vite reads
resolve.aliasfromvite.config.ts, nottsconfig.jsonpaths. Pairvite-tsconfig-pathsplugin if you want one source of truth. - The package is CJS and Vite's pre-bundler didn't catch it — add to
optimizeDeps.include.
Cannot use 'import.meta' outside a module
A .js file is being served as a classic script. Either rename to .mjs, set "type": "module" in the relevant package.json, or change the consumer to <script type="module">.
[plugin:vite:css] Preprocessor dependency "sass" not found
scss files require Sass installed alongside Vite. Vite no longer bundles preprocessors.
npm install -D sass-embedded # v6+ preferred — Dart Sass via node-api
Output:
added 3 packages, and audited 277 packages in 4s
"Server config is missing" after upgrading to v6
The Environment API restructured server/preview/build config. Old optimizeDeps at the top level still works, but per-environment overrides moved to environments.client.optimizeDeps.
Outdated Optimize Dep flashing in dev
The pre-bundle cache for a dep is stale because package.json changed. Vite usually auto-invalidates; force it with rm -rf node_modules/.vite && npm run dev.
Ecosystem integrations
Vite's plugin ecosystem is the largest of any modern bundler.
| Category | Plugins |
|---|---|
| Framework | @vitejs/plugin-react, @vitejs/plugin-vue, @sveltejs/vite-plugin-svelte, vite-plugin-solid, @preact/preset-vite, vite-plugin-svelte |
| Quality | vite-plugin-checker (TS + ESLint inline overlay), vite-plugin-inspect (transform pipeline viewer) |
| Assets | vite-plugin-svgr (SVG → React component), vite-plugin-static-copy, vite-imagetools |
| Bundle | rollup-plugin-visualizer, vite-bundle-analyzer, vite-plugin-compression |
| PWA / SSR | vite-plugin-pwa, vite-plugin-ssr (now Vike), @sveltejs/kit (uses Vite internally) |
| Mock / Proxy | vite-plugin-mock, vite-plugin-html, unplugin-icons |
| Unified | The unplugin-* family (unplugin-auto-import, unplugin-vue-components) — single plugin definition that runs in Vite, Rollup, webpack, esbuild, and Rspack |
Framework integrations beyond plugins:
- Astro embeds Vite as its internal build engine — every
astro devis a Vite dev server with content-collection and island plugins layered on top. - SvelteKit uses Vite for both dev and prod builds; the adapter layer determines the deploy target.
- Nuxt 3+ runs Vite (or Nitro for the server) — config exposed via
vite: { ... }innuxt.config.ts. - Remix (since the Vite preset) uses Vite for the dev/build pipeline; the old esbuild compiler is deprecated.
When NOT to use this
Vite's design optimises for browser-targeted apps and modern ESM tooling. The mismatches:
- Pure CJS bundling. If you're shipping a CJS-only library or coordinating a webpack-specific plugin chain, Vite's ESM-first dev path fights you. Use webpack or Rollup directly.
- Heavy AST manipulation in dev. Vite's per-file transform pipeline assumes cheap transforms. If you have a Babel macro chain that takes 500 ms per file, the dev experience degrades quickly — switch to a pre-compile step or pre-bundle the artefacts.
- Legacy-browser-first apps. Vite targets ES2020+ in dev.
@vitejs/plugin-legacycovers IE11 and old Safari for the prod build, but the dev experience still requires evergreen browsers. - Bundle-as-CommonJS workflows. For consumers that explicitly need a
.cjsartefact (older Node tooling, certain enterprise build pipelines), Rollup direct gives you more control than Vite's library mode.
See also
- JavaScript: vite — config, HMR, env vars, assets, plugins
- JavaScript: vitest — Vite-native test runner
- Concept: HTTP — dev server, base paths, asset URLs
- Packages: npm-vitest — testing companion