Guide

Vite fundamentals explained

You save a component file and the browser updates in under fifty milliseconds — no full page reload, no waiting for Webpack to rebundle three thousand modules. That responsiveness is why Vite (French for “fast”, pronounced /vit/) became the default build tool for React, Vue, and Svelte greenfield projects. Vite splits development and production: in dev it serves native ES modules from the source tree and uses esbuild only to pre-bundle heavy dependencies; in production it hands off to Rollup for tree-shaken, code-split static assets. This guide covers the dev server and HMR pipeline, vite.config.ts, plugins and framework presets, environment variables, CSS and asset handling, SSR and library mode basics, a Harbor Archive editorial dashboard worked example, a tooling decision table, common pitfalls, and a practitioner checklist — alongside our TypeScript guide and rendering modes guide.

What problem Vite solves

Classic bundler-first workflows (Webpack, Parcel v1) rebuild a dependency graph on every file change. As node_modules grows, cold starts stretch to minutes and hot module replacement (HMR) degrades into de facto full reloads. Developers compensate with smaller apps, fewer dependencies, or expensive CI machines — none of which fixes the architectural bottleneck.

Vite inverts the model during development:

  1. Serve source as ESM — the browser requests /src/main.ts directly; Vite transforms TypeScript, JSX, and Vue SFCs on demand per request.
  2. Pre-bundle dependencies once — CommonJS packages and deep import trees are converted to ESM with esbuild and cached in node_modules/.vite.
  3. HMR over the module graph — when a file changes, only its importers invalidate; the dev server pushes an update WebSocket message.

Production builds still bundle — browsers need optimized chunks, hashed filenames, and legacy fallbacks — but you do not pay bundling cost on every keystroke. That separation is the core design bet.

When Vite is a good fit

  • SPA and MPA frontends — dashboards, marketing sites, admin tools with client-side routing.
  • Component libraries — library mode emits ESM + UMD + type declarations.
  • Framework apps — official templates for React, Vue, Svelte, Solid, Preact, and Lit.
  • Teams already on modern ESM"type": "module" in package.json is the default in new Vite scaffolds.

Reach for something else when you need a full-stack framework with file-based routing and server components out of the box (see our Next.js guide), or when legacy Webpack loaders are non-negotiable and migration cost exceeds benefit.

Dev server, HMR, and the module graph

Running vite (or npm run dev) starts a dev server, default port 5173, that serves index.html at the project root. The entry script uses native <script type="module"> — no bundled bundle.js in dev.

Hot Module Replacement preserves application state while swapping changed modules. React Fast Refresh, Vue HMR, and Svelte HMR are enabled by official plugins. HMR fails gracefully: if a boundary cannot accept an update (e.g. you changed a module-level constant that affects exports), Vite falls back to a full reload and logs why.

Key dev-server options in vite.config.ts:

  • server.port and server.strictPort — fail fast if 5173 is taken instead of silently incrementing.
  • server.proxy — forward /api to a backend during local development without CORS workarounds.
  • server.https — local TLS for testing secure cookies or HTTP/2.
  • server.watch.ignored — exclude large generated dirs from file watchers.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      "/api": { target: "http://127.0.0.1:3000", changeOrigin: true },
    },
  },
});

The dependency pre-bundle step runs on first start and when lockfiles change. Force refresh with vite --force if you suspect stale cache after upgrading a major dependency.

Configuration, plugins, and framework presets

Vite config exports a function or object via defineConfig for TypeScript hints. Important top-level keys:

  • root — project root (default process.cwd()).
  • base — public path prefix when deployed to a subfolder or CDN (/app/).
  • resolve.alias — map @/ to src/ for cleaner imports.
  • build.outDir — production output (default dist).
  • build.rollupOptions — manual chunk splits, external globals.

Plugins extend the pipeline with Rollup-compatible hooks plus Vite-specific configureServer for dev middleware. First-party plugins:

  • @vitejs/plugin-react — Fast Refresh, automatic JSX runtime.
  • @vitejs/plugin-vue — Single-File Components, <script setup>.
  • @sveltejs/vite-plugin-svelte — Svelte 5 compiler integration.

Community plugins cover Tailwind v4 (@tailwindcss/vite), SVG as components, PWA service workers (vite-plugin-pwa), and visualizers for bundle analysis. Prefer plugins that list Vite 5+ compatibility; Webpack-only loaders do not port without rewrite.

Scaffolding new projects

npm create vite@latest my-app -- --template react-ts
cd my-app && npm install && npm run dev

Templates ship minimal configs you grow incrementally. For Tailwind CSS v4, add the Vite plugin and import your design tokens in the entry CSS file rather than maintaining a separate PostCSS pipeline unless you need legacy plugins.

Environment variables, CSS, and static assets

Vite exposes env vars prefixed with VITE_ to client code via import.meta.env:

// .env.local (never commit secrets)
VITE_API_BASE=https://api.staging.example.com

// src/api.ts
const base = import.meta.env.VITE_API_BASE;

Only VITE_* keys are inlined at build time — do not put private API keys in client bundles. Server-side secrets belong in backend services or SSR load functions, not .env files consumed by browser code. import.meta.env.MODE distinguishes development vs production; import.meta.env.DEV and PROD are boolean shortcuts.

CSS imports from JS are modules by default in dev with HMR. PostCSS (Autoprefixer, nesting) runs automatically when postcss.config.js exists. CSS modules use *.module.css naming. Static assets in public/ copy verbatim to dist/ root; imported assets from src/ get hashed filenames and size warnings above 4 KB.

Production builds and deployment

vite build runs Rollup with esbuild transpilation for speed. Output defaults:

  • Code splitting — dynamic import() creates async chunks loaded on navigation.
  • Tree shaking — dead ESM exports drop when side-effect free.
  • Minification — esbuild minify by default; optional Terser for edge cases.
  • Source maps — enable build.sourcemap: true for production debugging (hide from public CDN if sensitive).

Preview locally with vite preview — serves dist/ on port 4173. Deploy dist/ to any static host (S3 + CloudFront, Netlify, Cloudflare Pages). Configure base if assets live under a subpath. Pair with our CDN guide for cache headers on hashed assets.

SSR and SSG are not built into core Vite; frameworks add them: Vite SSR APIs (vite.ssrLoadModule) power custom servers; vite-plugin-ssr (Vike) and meta-frameworks layer routing. For static prerendering, vite build --ssrManifest plus a prerender script is common. Understand rendering trade-offs in our SSR/CSR/SSG guide before picking architecture.

Library mode

Publishing a component library? Set build.lib.entry, build.lib.name, and rollupOptions.external for peer dependencies (React, Vue). Vite emits ESM and optionally UMD/IIFE formats with vite-plugin-dts for TypeScript declarations.

Worked example: Harbor Archive editorial dashboard

Harbor Archive’s newsroom needed an internal SPA where editors draft articles, preview markdown, and schedule publication slots against a REST API. Requirements: sub-second HMR for a team of six, TypeScript strict mode, Tailwind v4 tokens shared with the public site, and production bundles under 200 KB gzipped for the initial route.

Stack and config highlights

  • Templatereact-ts scaffold with @vitejs/plugin-react.
  • Routing — React Router data APIs; route-level lazy() for the heavy markdown editor chunk.
  • API proxyserver.proxy['/api'] to the Node draft service in dev; nginx path routing in staging.
  • EnvVITE_API_BASE per environment; CI injects staging URL at build time.
  • Quality gatesvite build in CI plus rollup-plugin-visualizer budget check failing if main chunk exceeds 180 KB.
// vite.config.ts (excerpt)
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: { alias: { "@": "/src" } },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          editor: ["@codemirror/state", "@codemirror/view"],
        },
      },
    },
  },
});

Editors reported dev server ready in 1.2 seconds on laptops (versus 45 seconds on the retired Webpack config). Production Lighthouse performance scored 94 on the login route after code-splitting the CodeMirror editor. The team documented that SSR was unnecessary — the dashboard is auth-gated and SEO-irrelevant, so pure CSR via Vite matched requirements without Next.js complexity.

Tooling decision table

NeedPreferWhy
Fast SPA dev with React/Vue/SvelteViteNative ESM + esbuild pre-bundle; industry default templates
Full-stack React with RSC and API routesNext.jsFile routing, server components, deployment integrations
Legacy enterprise app on Webpack 4 loadersGradual migrationVite migration guide or Module Federation until plugins exist
All-in-one JS runtime + bundlerBunSee our Bun guide for server-first workflows
Zero-config prototype, small scopeVite templatesFaster than tuning Webpack from scratch
Publish npm component libraryVite library modeESM + types + watch mode for local npm link consumers

Common pitfalls

  • Putting secrets in VITE_* — every client bundle exposes them; use server proxies instead.
  • Wrong base path — blank pages in staging when assets 404 under /app/assets/; set base: '/app/' to match deploy path.
  • CommonJS-only dependencies — pre-bundle usually fixes them; otherwise add to optimizeDeps.include or replace the package.
  • HMR broken behind corporate proxy — configure server.hmr.host and WebSocket port forwarding.
  • Duplicating React — linked monorepo packages can resolve two React copies; dedupe with resolve.dedupe: ['react'].
  • Expecting Webpack loader ecosystem — verify Vite plugin exists before assuming loader parity.
  • Skipping vite preview before deploy — dev server behavior differs from static dist/; preview catches routing and base bugs.

Practitioner checklist

  • Pin Vite and plugin versions; read migration notes for major Vite releases (5 to 6).
  • Commit package-lock.json or pnpm-lock.yaml; CI runs npm ci then vite build.
  • Set server.proxy for API routes in dev; never point production SPAs at localhost.
  • Prefix public client env vars with VITE_; audit bundles with vite build --debug if unsure.
  • Use route-level code splitting for heavy editors, charts, and 3D libraries.
  • Enable build.sourcemap for error tracking services; restrict map URLs in production.
  • Run vite preview in CI smoke tests against built dist/.
  • Configure CDN long-cache headers for hashed assets; short-cache index.html.
  • Document when to eject to SSR (auth, SEO, TTFB) vs staying CSR.
  • Clear node_modules/.vite cache when dependency resolution behaves oddly after upgrades.

Key takeaways

  • Vite serves native ESM in development and bundles with Rollup for production — fast HMR without sacrificing optimized output.
  • esbuild pre-bundles dependencies; source files transform on demand per request.
  • vite.config.ts, official framework plugins, and VITE_ env vars are the daily toolkit.
  • Deploy dist/ to static hosting; use base and code splitting for real-world performance.
  • Choose Vite for SPAs and libraries; reach for Next.js or custom SSR when server rendering is a first-class requirement.

Related reading