How to Diagnose Slow Vite Projects
Vite is usually extremely fast in small projects. As a codebase grows, more plugins, heavier dependencies, monorepo links, and thousands of modules can turn startup, HMR, or production builds into real bottlenecks.
The common mistake is to tune configuration before measuring. A better workflow is:
Measure → Isolate → Profile → Change → Remeasure
Define what is slow
"Vite is slow" is not specific enough. Separate:
- cold dev-server startup;
- initial page load;
- HMR;
- production build.
Each stage can have a different root cause.
Record a baseline
Measure before changing configuration. Do not mix cold and warm runs, and keep the environment as stable as possible.
For dev-page issues, browser extensions and DevTools cache settings can also distort the result.
Inspect plugins
Vite exposes transform debugging:
vite --debug plugin-transform
Look for plugins that run across too many files or perform expensive work without narrow filters.
Important hooks include:
config
configResolved
buildStart
resolveId
load
transform
A small per-file transform cost can become significant across thousands of modules.
Inspect transform timing and import waterfalls
vite --debug transform
This helps reveal expensive files and deep dependency chains:
main.tsx
↓
Dashboard.tsx
↓
charts/index.ts
↓
heavy-chart-library
On-demand transforms are one of Vite's strengths, but a long chain can still create a waterfall.
Audit barrel files with evidence
Barrels are not automatically bad, but a very broad index.ts can expand the amount of graph that needs resolution.
Instead of:
import { formatMoney } from "@/utils"
sometimes a direct import is worth testing:
import { formatMoney } from "@/utils/format-money"
Only keep the change if the measurement supports it.
Capture CPU profiles
For development:
vite --profile --open
For builds:
vite build --profile
The generated .cpuprofile can show where CPU time is actually being spent.
Use server.warmup selectively
When a small set of expensive modules is always needed on the first route, targeted warmup can help:
export default defineConfig({
server: {
warmup: {
clientFiles: [
"./src/pages/dashboard.tsx",
"./src/lib/heavy-utils.ts",
],
},
},
})
Do not warm the entire application. That simply moves the cost into startup.
Do not treat --force as a permanent fix
Dependency optimization is automatic. If a project constantly needs vite --force, investigate the actual cache invalidation or linked-dependency behavior.
What changed in Vite 8?
Vite 8, released in March 2026, moved to Rolldown as a unified bundler. The official Vite release describes substantial build-performance improvements.
That still does not replace profiling. A slow custom plugin or a pathological import graph remains real work even with a faster bundler.
Vite 8.1 also introduced an experimental bundled dev mode aimed at very large applications where module count itself is the bottleneck. It is not a default recommendation for normal projects.
A repeatable audit workflow
1. Scope
cold start:
first load:
HMR:
build:
2. Environment
- Node version
- Vite version
- package manager
- plugin list
- monorepo/linking
3. Plugin timing
vite --debug plugin-transform
4. Transform timing
vite --debug transform
5. CPU profiling
vite --profile --open
vite build --profile
6. Make one targeted change
For example:
- narrow a plugin filter;
- remove expensive work from startup hooks;
- replace a problematic barrel import;
- warm a small hot set;
- upgrade Vite.
7. Remeasure
If the before/after result is not measurable, do not claim a performance win.
Conclusion
Vite performance is primarily a profiling problem, not a collection of magic configuration flags:
Slow Vite
↓
Classify
↓
Measure plugins/transforms
↓
CPU profile
↓
Find bottleneck
↓
Targeted change
↓
Remeasure
That workflow produces a defensible result instead of a pile of random tweaks.