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

bash
# 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.

bash
# 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.

bash
# 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.x is in maintenance.
  • Vite drops Node EOL versions on each major bump. 7.x requires Node 20.19+ or 22.12+; 6.x accepted Node 18.
  • ESM-only since 5.xvite.config.js (CJS) still works because Vite transpiles the config on the fly, but Vite itself cannot be require()'d.
  • Always a dev dependencyvite build emits a static dist/ 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:

PluginPurpose
@vitejs/plugin-reactReact Fast Refresh + JSX transform.
@vitejs/plugin-react-swcSame, but uses SWC instead of Babel — faster.
@vitejs/plugin-vueVue 3 SFC support.
@vitejs/plugin-vue-jsxVue 3 JSX/TSX support.
@vitejs/plugin-legacyPolyfill / legacy-browser bundle alongside the modern one.
@vitejs/plugin-basic-sslSelf-signed HTTPS for local dev.
vite-plugin-svelteSvelte / SvelteKit integration.
vite-tsconfig-pathsResolve TS paths aliases.
vite-plugin-pwaService 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

ToolTrade-off
webpackMature, huge plugin ecosystem. Slower dev startup; complex config. The "old guard" — still widely used in enterprise.
rollupLibrary-focused bundler. Vite uses it under the hood for prod builds. Pick directly for publishing libraries.
esbuildExtremely fast Go-based bundler. Lower-level than Vite; less ergonomic for SPA dev (no built-in HMR pipeline, less plugin reach).
parcelZero-config bundler. Friendly for prototypes; smaller ecosystem and slower than Vite for large apps.
turbopackVercel's Rust bundler, default for Next.js dev (and now stable for prod). Tied closely to Next; not a general drop-in.
rspackByteDance's Rust port of webpack — drop-in webpack replacement. Different niche from Vite (config-compatible with webpack, not ESM-first).
rolldownThe Rust replacement for Rollup, written by the Vite team. Vite is migrating to it under the hood; not normally used directly.

Common gotchas

  1. import.meta.env vs process.env. Vite exposes env vars under import.meta.env, not Node's process.env. Only variables prefixed with VITE_ are exposed to client code — anything else stays server-side, even in a .env file. Forgetting the prefix produces silent undefined at runtime.
  2. CommonJS dependency interop. Vite's dev server expects ESM. CJS deps go through optimizeDeps pre-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 to optimizeDeps.include or use a default import.
  3. 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.
  4. base config for non-root deployments. Deploying to example.com/app/ requires base: "/app/" in vite.config.ts. Forgetting it produces a working dev server and a prod build with broken asset URLs that all 404.
  5. 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.
  6. Source maps in libraries. When publishing a library built with vite build --mode lib, omit sourcemap: true unless you want to ship .map files to consumers — they bloat the published package and leak source paths.
  7. The define option does string replacement, not eval. define: { __VERSION__: "1.0" } literally substitutes the string 1.0 in source — usually you want JSON.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.

typescript
// 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:

typescript
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.

typescript
// 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.

typescript
// 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" },
    },
  },
});
json
{
  "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.

typescript
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.

typescript
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.

bash
# Compute Subresource Integrity for assets
npm install -D vite-plugin-sri3

Output:

text
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.

typescript
optimizeDeps: {
  include: ["lodash-es", "date-fns", "@mui/material"],
  exclude: ["@my/local-pkg"],
  esbuildOptions: { target: "es2022" },
}

Profile a slow plugin

bash
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:

text
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.

typescript
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.

typescript
// 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 → ToHighlights
3 → 4esbuild moved to ESM; define no longer auto-stringifies values. Plugins targeting Rollup 2 needed updates for Rollup 3.
4 → 5Node 18+ required. CJS Node API removed — require("vite") no longer works; switch to await import("vite"). splitVendorChunkPlugin deprecated; write manualChunks directly.
5 → 6Environment API graduates from experimental. build.modulePreload.polyfill defaults to true. Sass legacy API removed — use sass-embedded.
6 → 7Node 20.19+ / 22.12+ required. Rolldown rollout begins behind a flag. transformIndexHtml hook order normalised; check custom plugins.

defineConfig mode signature

typescript
// 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.

  1. import.meta.env leak prevention. Only VITE_* 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 by import.meta.env.SSR, ensure those vars are not stringified into the client chunk.
  2. server.host: true binds on every interface. On a shared LAN, anyone can hit your dev server and exfiltrate source. Use host: "127.0.0.1" (default) or set up an explicit allowlist.
  3. server.fs.allow restricts 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, widen fs.allow explicitly rather than disabling the check.
  4. CSP for production HTML. Vite's default <script type="module"> inline tag requires 'unsafe-inline' unless you nonce-inject. The vite-plugin-csp-guard plugin computes hashes for every emitted inline script.
  5. Source maps in prod. sourcemap: true ships maps to the public bucket — useful for debugging, but they leak the entire source tree. Use sourcemap: "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.alias from vite.config.ts, not tsconfig.json paths. Pair vite-tsconfig-paths plugin 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.

bash
npm install -D sass-embedded   # v6+ preferred — Dart Sass via node-api

Output:

text
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.

CategoryPlugins
Framework@vitejs/plugin-react, @vitejs/plugin-vue, @sveltejs/vite-plugin-svelte, vite-plugin-solid, @preact/preset-vite, vite-plugin-svelte
Qualityvite-plugin-checker (TS + ESLint inline overlay), vite-plugin-inspect (transform pipeline viewer)
Assetsvite-plugin-svgr (SVG → React component), vite-plugin-static-copy, vite-imagetools
Bundlerollup-plugin-visualizer, vite-bundle-analyzer, vite-plugin-compression
PWA / SSRvite-plugin-pwa, vite-plugin-ssr (now Vike), @sveltejs/kit (uses Vite internally)
Mock / Proxyvite-plugin-mock, vite-plugin-html, unplugin-icons
UnifiedThe 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 dev is 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: { ... } in nuxt.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-legacy covers 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 .cjs artefact (older Node tooling, certain enterprise build pipelines), Rollup direct gives you more control than Vite's library mode.

See also