
Vite: Why the Dev Server Feels Instant
It does not bundle during development. Understanding that one decision explains the speed, the plugin model, and the two places it still surprises people.
Every bundler-based dev server does the same thing on startup: read your entire dependency graph, transform it, bundle it, then serve it. The cost scales with project size, which is why the third year of a codebase starts with a coffee break.
Vite's development server does not bundle. Browsers support ES modules natively, so it serves your source files directly and transforms each one on request.
The consequence is that startup time is roughly constant. A twenty-file project and a two-thousand-file project both start in about the same time, because neither one required the whole graph to be processed first.
Hot updates are proportional to the edit
Change one component and the server invalidates that module and its importers. Not the bundle — the module. The browser fetches the new version and swaps it. This is why HMR stays fast in a big app instead of degrading as the app grows.
Dependencies get special treatment
Your node_modules are not served file-by-file — some packages ship hundreds of tiny modules and that would mean hundreds of requests. They are pre-bundled once with esbuild and cached:
export default defineConfig({
optimizeDeps: {
include: ['some-cjs-package'], // force pre-bundling
exclude: ['my-local-linked-lib'] // keep a linked package live
},
})
This is the setting you reach for when a linked local package will not hot-reload, which is the most common "why is Vite being weird" question.
Production does bundle
This is the trap. Development is unbundled; production goes through Rollup. So a difference between dev and build is not a mystery — it is two different code paths, and the one users get is the second one.
Practical rule: run vite build && vite preview before you believe anything about performance, and put the built output — not the dev server — in front of Lighthouse.
Keeping the output honest
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
chunkSizeWarningLimit: 600,
}
Splitting the framework from application code means a content change does not invalidate the cached vendor chunk. And read the size table Vite prints after every build — it is the cheapest performance monitoring available, and it is already in your terminal.
Why it is the default now
The speed is what people talk about, but the durable advantage is that the plugin API is shared with Rollup, so the ecosystem is one ecosystem. Framework choice stopped requiring a bespoke build setup, which is the kind of consolidation that makes every project after it cheaper.
Resources
- Repo: vitejs/vite
- Docs: vite.dev
- Video walkthroughs: YouTube: vite config optimization tutorial
- Related: Biome: one toolchain instead of two
Need this built properly?
I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.


