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:
- Serve source as ESM — the browser requests
/src/main.tsdirectly; Vite transforms TypeScript, JSX, and Vue SFCs on demand per request. - Pre-bundle dependencies once — CommonJS packages and deep import trees are converted to ESM with esbuild and cached in
node_modules/.vite. - 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"inpackage.jsonis 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.portandserver.strictPort— fail fast if 5173 is taken instead of silently incrementing.server.proxy— forward/apito 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 (defaultprocess.cwd()).base— public path prefix when deployed to a subfolder or CDN (/app/).resolve.alias— map@/tosrc/for cleaner imports.build.outDir— production output (defaultdist).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: truefor 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
- Template —
react-tsscaffold with@vitejs/plugin-react. - Routing — React Router data APIs; route-level
lazy()for the heavy markdown editor chunk. - API proxy —
server.proxy['/api']to the Node draft service in dev; nginx path routing in staging. - Env —
VITE_API_BASEper environment; CI injects staging URL at build time. - Quality gates —
vite buildin CI plusrollup-plugin-visualizerbudget 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
| Need | Prefer | Why |
|---|---|---|
| Fast SPA dev with React/Vue/Svelte | Vite | Native ESM + esbuild pre-bundle; industry default templates |
| Full-stack React with RSC and API routes | Next.js | File routing, server components, deployment integrations |
| Legacy enterprise app on Webpack 4 loaders | Gradual migration | Vite migration guide or Module Federation until plugins exist |
| All-in-one JS runtime + bundler | Bun | See our Bun guide for server-first workflows |
| Zero-config prototype, small scope | Vite templates | Faster than tuning Webpack from scratch |
| Publish npm component library | Vite library mode | ESM + types + watch mode for local npm link consumers |
Common pitfalls
- Putting secrets in
VITE_*— every client bundle exposes them; use server proxies instead. - Wrong
basepath — blank pages in staging when assets 404 under/app/assets/; setbase: '/app/'to match deploy path. - CommonJS-only dependencies — pre-bundle usually fixes them; otherwise add to
optimizeDeps.includeor replace the package. - HMR broken behind corporate proxy — configure
server.hmr.hostand 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 previewbefore deploy — dev server behavior differs from staticdist/; preview catches routing andbasebugs.
Practitioner checklist
- Pin Vite and plugin versions; read migration notes for major Vite releases (5 to 6).
- Commit
package-lock.jsonorpnpm-lock.yaml; CI runsnpm cithenvite build. - Set
server.proxyfor API routes in dev; never point production SPAs at localhost. - Prefix public client env vars with
VITE_; audit bundles withvite build --debugif unsure. - Use route-level code splitting for heavy editors, charts, and 3D libraries.
- Enable
build.sourcemapfor error tracking services; restrict map URLs in production. - Run
vite previewin CI smoke tests against builtdist/. - 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/.vitecache 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, andVITE_env vars are the daily toolkit.- Deploy
dist/to static hosting; usebaseand 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
- React fundamentals explained — components, hooks, and patterns Vite templates scaffold by default
- Vue fundamentals explained — SFCs and reactivity with
@vitejs/plugin-vue - TypeScript fundamentals explained — strict types Vite runs without a separate compile step
- SSR, CSR, SSG and ISR explained — when a Vite SPA is enough and when you need server rendering