Vite Dev Speed Comes From Native ESM, Not Faster Bundling
Vite avoids slow cold starts by serving source files as native ES modules in dev and pre-bundling CommonJS dependencies on demand with esbuild. This keeps startup fast and HMR stable when configured with optimizeDeps.
01 Jan 2026, 14:13 UTC

Slow cold starts and full rebuilds on every save are the concrete problem Vite was designed to remove. The useful takeaway is that Vite does not bundle your app in development at all. It serves your source files as native ES modules and only transforms what the browser requests, with a targeted pre-bundle step for CommonJS and legacy dependencies.
Serve source as ESM, transform on demand
In a traditional bundler the dev server builds a dependency graph up front and serves a bundle. Vite inverts that. The dev server returns bare import URLs for your source code. The browser fetches modules directly, and Vite rewrites imports, injects the HMR runtime, and uses esbuild for fast TypeScript and JSX transforms.
ESM means ECMAScript modules, the native import/export format browsers understand. HMR is Hot Module Replacement, updating modules in the running app without a full reload. Because the server does not walk the whole graph at startup, initial startup is proportional to what you actually request.
You can check this behavior by running a minimal Vite project and opening the network tab. Requests for your own files appear as individual .js modules with Vite query parameters rather than a single bundled entry. That is the signal that serving is on demand.
Pre-bundling CommonJS with optimizeDeps
Native ESM works well for your own code, but many npm packages still ship CommonJS or have deep dependency trees. Vite pre-bundles those dependencies on first request using esbuild into a single optimized chunk stored under node_modules/.vite. The cache is on disk and reused across restarts.
The main control knob is optimizeDeps. It decides which packages are pre-bundled, which are included explicitly, and which are excluded. Pre-bundling adds an initial cost the first time a dependency is hit, but it removes repeated on-the-fly transforms and stabilizes HMR for packages that are not ESM friendly.
Configuration lives in vite.config.js at the project root. Edit with write permission to the project directory. A typical adjustment for heavy CommonJS libraries looks like:
// vite.config.js
export default {
optimizeDeps: {
include: ['lodash', 'date-fns']
}
}
Risk: adding entries increases first start work and disk cache size. Removing entries can cause Vite to fall back to on-demand transforms, which may increase request latency for those packages. Changes affect development only.
Worked example: React app with lodash and date-fns
Start from Vite defaults and create a React app. In a terminal with project write access run the scaffolding command with a placeholder name and template, then start the dev server in the project folder.
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
With no optimizeDeps changes, the first import of lodash or date-fns triggers an on-demand pre-bundle. After the first request the optimized chunks appear under node_modules/.vite. Subsequent edits to app code trigger only esbuild transforms for your files and HMR updates, keeping the module graph small.
Adding the two packages to optimizeDeps.include makes the pre-bundle happen eagerly at server start. You can compare startup behavior by inspecting node_modules/.vite after first start and observing cache invalidation after changing optimizeDeps. The decision point is whether the eager cost is worth more predictable HMR for a library you import in many places.
Trade-offs and limits
Pre-bundling adds initial cost and disk cache. The native ESM model assumes browser support for import maps and bare specifiers, and it does not replicate production bundling, code-splitting, or minification.
Non-ESM plugins, SSR code that relies on Node require, or monorepo hoisting can cause resolution quirks that need explicit config such as ssr.noExternal or optimizeDeps.exclude. Assumptions from client dev do not always transfer to SSR and library mode, which have different resolution rules.
Behavior of optimizeDeps and pre-bundling changed across major Vite releases, so configuration names and defaults may differ between releases. Verify with your current release notes.
Start with Vite defaults, measure dev server startup and HMR latency for your app, add optimizeDeps entries for heavy CommonJS libraries you use widely, and keep source as ESM where possible. For production, rely on Rollup output and test library mode if you ship components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.