Vite 29 ⚡ Performance Auditing — Bundle analysis & chunk sizing
Vite’s bundle output is not a single number. It is a set of chunks, each with its own raw size, its own gzipped size, and its own role in the application’s load sequence. The audit is the discipline of reading that set: which chunks are on the critical path, which are lazy-loaded, which are duplicated, and which are larger than the code they contain warrants. The tools that produce the reading are the visualizer for the composition, the build’s chunk-size warning for the threshold, and the Rolldown DevTools for the chunk graph when Vite 8 is in use. The three answer different questions, and the audit is the practice of asking all three.
The most important distinction in a Vite bundle audit is the one between the entry chunk and the lazy chunks. A 500 KB vendor chunk that loads on every page is a problem; a 500 KB editor chunk that loads only when the user opens the editor is not. The build’s chunk-size warning does not make this distinction. It warns on every chunk above the limit, regardless of whether the chunk is on the critical path or behind a dynamic import . The audit’s job is to make the distinction, and the visualizer’s job is to show which chunks are which.
Key point: rollup-plugin-visualizer produces a treemap of the bundle’s composition. build.chunkSizeWarningLimit sets the threshold for the build’s warning. In Vite 8, build.rolldownOptions.output.advancedChunks replaces manualChunks and provides declarative chunk grouping with priority and size constraints. The visualizer shows what is in the bundle; the chunk configuration controls how it is split.
Why the audit needs both the composition and the chunk graph
The composition problem. A bundle that is larger than expected has a cause: a dependency that is larger than assumed, a duplicate package, or a library that is imported wholesale instead of tree-shaken. The rollup-plugin-visualizer produces an interactive treemap where each rectangle is a module, and the rectangle’s area is the module’s contribution to the bundle . The visualizer’s gzipSize: true option adds the gzipped size, which is the number that matters for the user’s download . The treemap is the composition audit: it shows what is in the bundle and how much space each module occupies.
The chunk graph problem. The composition audit answers “what is in the bundle.” The chunk graph audit answers “how is the bundle split, and which chunks are on the critical path.” The build’s chunk-size warning is the first signal: a chunk above the limit is a chunk that may be too large for its role . The warning does not distinguish between the entry chunk and a lazy chunk, so the developer must check the chunk’s role manually. In Vite 8, the Rolldown DevTools provides a chunk view that shows the references between chunks, which is the graph the audit needs .
The chunk configuration problem. The composition and the chunk graph are the diagnosis. The treatment is the chunk configuration. In Vite 7 and earlier, build.rollupOptions.output.manualChunks is the mechanism: a function that receives the module id and returns the chunk name, or an object that maps chunk names to module arrays . In Vite 8, advancedChunks is the recommended approach: a declarative configuration with groups, each with a name, a test pattern, a priority, and optional minSize and maxSize constraints . The maxSize constraint is the auto-split mechanism: a chunk that exceeds the size is split automatically, which is the feature that manualChunks did not provide.
The trade-off. The visualizer adds a build step and a dependency. The chunk-size warning is free but does not distinguish between the critical path and the lazy path. The advancedChunks configuration is more complex than manualChunks, and the maxSize auto-split can produce chunks that are smaller than the network overhead justifies. The audit is the discipline of using the tools together, not the discipline of picking one.
a. The visualizer and the composition audit
The rollup-plugin-visualizer package is the standard composition tool. It is installed as a dev dependency and added to the plugins array in vite.config.ts. The visualizer() call produces an HTML report when the build runs.
// vite.config.ts — the visualizer configuration
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
// The visualizer produces the report after the build.
visualizer({
// The report opens automatically in the browser.
open: true,
// The report is written to the dist directory.
filename: 'dist/stats.html',
// The gzip size is the number that matters for the download.
gzipSize: true,
// The brotli size is the alternative compression metric.
brotliSize: true,
}),
],
});
The report’s default template is the treemap: a rectangular layout where each module’s area is proportional to its size. The sunburst template is the circular view, and the network template is the graph that answers “why is this module included” . The treemap is the most direct for finding the largest modules; the network is the most direct for finding the import chain that pulled a module in.
# Run the build to generate the report.
npx vite build
# The report is at dist/stats.html.
# Open it in the browser.
The visualizer’s findings are the starting point for the optimization. A large moment.js rectangle is a candidate for replacement with date-fns. A large lodash rectangle is a candidate for lodash-es with named imports. A duplicate React rectangle is a candidate for resolve.dedupe . The visualizer does not fix the bundle; it shows the composition that the fix must address.
b. The chunk-size warning and the advancedChunks configuration
The build’s chunk-size warning is the threshold audit. The build.chunkSizeWarningLimit option sets the limit in kilobytes. The default is 500 KB, and a chunk above the limit produces the “Some chunks are larger than 500 kB” warning .
// vite.config.ts — the chunk-size warning limit
import { defineConfig } from 'vite';
export default defineConfig({
build: {
// The warning limit in kilobytes.
// The default is 500.
chunkSizeWarningLimit: 400,
},
});
The limit is a signal, not a solution. Raising the limit hides the warning without reducing the bundle . The correct response to the warning is to check whether the chunk is on the critical path and whether it can be split or lazy-loaded. A chunk that is lazy-loaded and above the limit is acceptable; a chunk that is in the entry and above the limit is the problem.
In Vite 8, the chunk configuration is advancedChunks. The groups array declares the split strategy, and each group has a name, a test pattern, and a priority. The maxSize option auto-splits a group that exceeds the size .
// vite.config.ts — the advancedChunks configuration
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 20,
},
{
name: 'charts',
test: /[\\/]node_modules[\\/](recharts|d3)[\\/]/,
priority: 15,
// The chart chunk is auto-split if it exceeds 300 KB.
maxSize: 300000,
},
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 10,
maxSize: 500000,
},
],
},
},
},
},
});
The priority controls which group wins when a module matches more than one test. The react-vendor group has a higher priority than the vendor group, so React is extracted into its own chunk before the vendor group is considered . The maxSize on the vendor group is the safety net: a vendor chunk that grows beyond 500 KB is split automatically, which prevents the single massive vendor chunk that a catch-all grouping produces.
The advancedChunks configuration is the Vite 8 mechanism. A project on Vite 7 uses manualChunks instead, and the function form is the one that provides the same precision .
// vite.config.ts — the Vite 7 manualChunks function
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor';
if (id.includes('node_modules/recharts')) return 'charts';
if (id.includes('node_modules')) return 'vendor';
},
},
},
},
});
The function form is more verbose than the object form, but it is the only form that can match on the module id. The object form (manualChunks: { vendor: ['react', 'react-dom'] }) is the simple case, and it does not support the pattern matching that a real chunk strategy needs.
Complete Example Session
// ============================================
// PART 1: THE VISUALIZER
// ============================================
// vite.config.ts
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
open: true,
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true,
}),
],
});
// ============================================
// PART 2: THE CHUNK WARNING LIMIT
// ============================================
export default defineConfig({
build: {
chunkSizeWarningLimit: 400,
},
});
// The build warns on any chunk above 400 KB.
// The warning is the signal; the visualizer is the diagnosis.
// ============================================
// PART 3: THE ADVANCED CHUNKS (VITE 8)
// ============================================
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 20,
},
{
name: 'charts',
test: /[\\/]node_modules[\\/](recharts|d3)[\\/]/,
priority: 15,
maxSize: 300000,
},
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 10,
maxSize: 500000,
},
],
},
},
},
},
});
// ============================================
// PART 4: THE MANUAL CHUNKS (VITE 7)
// ============================================
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor';
if (id.includes('node_modules/recharts')) return 'charts';
if (id.includes('node_modules')) return 'vendor';
},
},
},
},
});
// ============================================
// PART 5: THE COMMON FINDINGS
// ============================================
// A large moment.js rectangle in the visualizer:
// → Replace with date-fns or dayjs.
//
// A large lodash rectangle:
// → Use lodash-es with named imports.
//
// A duplicate React rectangle:
// → Add resolve.dedupe: ['react', 'react-dom'].
//
// A large icon library:
// → Use direct imports instead of the barrel file.
// ============================================
// PART 6: THE LAZY-LOADED CHUNK
// ============================================
// A large editor chunk behind a dynamic import:
const Editor = lazy(() => import('./Editor'));
// The chunk is above the warning limit, but it is lazy-loaded.
// The warning is acceptable because the chunk is not on the critical path.
// ============================================
// PART 7: THE CRITICAL PATH CHUNK
// ============================================
// A large entry chunk:
// The warning is not acceptable.
// The chunk must be split or the large dependency must be removed.
// ============================================
// PART 8: THE GZIP SIZE
// ============================================
// The visualizer's gzipSize option adds the gzipped size.
// The gzipped size is the number that matters for the download.
// A 500 KB raw chunk may be 150 KB gzipped.
// The gzipped size is the one to compare against the budget.
// ============================================
// PART 9: THE VERIFICATION
// ============================================
// 1. Run the build with the visualizer.
// 2. Open dist/stats.html.
// 3. Identify the largest modules.
// 4. Check the chunk graph for the critical path.
// 5. Apply the chunk configuration.
// 6. Rebuild and compare the visualizer reports.
// ============================================
// PART 10: THE CHECKLIST
// ============================================
// [ ] The visualizer is installed and configured.
// [ ] The gzipSize option is enabled.
// [ ] The chunkSizeWarningLimit is set.
// [ ] The advancedChunks (Vite 8) or manualChunks (Vite 7) is configured.
// [ ] The largest modules are identified.
// [ ] The critical path chunks are distinguished from the lazy chunks.
// [ ] The duplicate packages are checked.
// [ ] The build is rebuilt and compared.
The ten parts covered the visualizer, the warning limit, the advanced chunks, the manual chunks, the common findings, the lazy chunk, the critical path chunk, the gzip size, the verification, and the checklist. The through-line is that the visualizer shows the composition, the warning shows the threshold, and the chunk configuration is the treatment.
Quick Reference
The visualizer options
| Option | Purpose |
|---|---|
open | Open the report after the build |
filename | The report output path |
gzipSize | Include the gzipped size |
brotliSize | Include the brotli size |
template | treemap, sunburst, network, raw-data |
The chunk warning
| Option | Default | Purpose |
|---|---|---|
build.chunkSizeWarningLimit | 500 | The warning threshold in KB |
The chunk configuration
| Vite version | Mechanism | Location |
|---|---|---|
| Vite 8 | advancedChunks | build.rolldownOptions.output |
| Vite 7 | manualChunks | build.rollupOptions.output |
The common findings
| Finding | Fix |
|---|---|
Large moment.js | Replace with date-fns |
Large lodash | Use lodash-es with named imports |
| Duplicate React | resolve.dedupe: ['react', 'react-dom'] |
| Large icon library | Direct imports instead of barrel file |
Best Practices
✅ Do This:
// Use the visualizer with gzipSize
visualizer({ open: true, gzipSize: true }) // ✅
// Set a chunk warning limit
build: { chunkSizeWarningLimit: 400 } // ✅
// Use advancedChunks for Vite 8
advancedChunks: { groups: [{ name: 'react-vendor', test: /react/ }] } // ✅
// Check whether a large chunk is on the critical path
// A lazy chunk above the limit is acceptable. // ✅
❌ Don’t Do This:
// Raise the warning limit without investigating
build: { chunkSizeWarningLimit: 1000 } // hides the problem // ❌
// Use a catch-all vendor chunk
manualChunks: { vendor: ['react', 'lodash', 'recharts'] } // one huge chunk // ❌
// Ignore the duplicate packages
// The visualizer shows them; the dedupe fixes them. // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The visualizer shows no data | The plugin is not in the plugins array | Add the visualizer() call |
| The warning does not distinguish lazy chunks | The warning is size-only | Check the chunk’s role manually |
| The vendor chunk is too large | A catch-all grouping | Split the heavy libraries into their own groups |
| The gzip size is not shown | gzipSize: false | Set gzipSize: true |
Real-World Examples
1. The visualizer
visualizer({ open: true, filename: 'dist/stats.html', gzipSize: true })
2. The warning limit
build: { chunkSizeWarningLimit: 400 }
3. The advanced chunk
advancedChunks: { groups: [{ name: 'react-vendor', test: /react/, priority: 20 }] }
4. The manual chunk
manualChunks(id) { if (id.includes('react')) return 'react-vendor'; }
5. The lazy chunk
const Editor = lazy(() => import('./Editor'));
6. The gzip size
visualizer({ gzipSize: true })
7. The duplicate React
resolve: { dedupe: ['react', 'react-dom'] }
8. The date library
// moment → date-fns
9. The lodash import
// lodash → lodash-es with named imports
10. The verification
npx vite build && open dist/stats.html
Visual
The Audit Flow
┌──────────────────────────────────────────────────────────────┐
│ FROM COMPOSITION TO CONFIGURATION │
│ │
│ Visualizer │
│ ┌─────────────────────┐ │
│ │ dist/stats.html │ Shows the largest modules │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ Chunk warning │
│ ┌─────────────────────┐ │
│ │ "Some chunks are │ Shows the chunks above the limit │
│ │ larger than 500 kB" │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ Chunk configuration │
│ ┌─────────────────────┐ │
│ │ advancedChunks │ Splits the heavy dependencies │
│ │ manualChunks │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ Rebuild and compare │
│ ┌─────────────────────┐ │
│ │ The report shows │ The chunks are smaller │
│ │ the improvement │ │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
The Critical Path vs Lazy Path
┌──────────────────────────────────────────────────────────────┐
│ A LARGE CHUNK IS NOT ALWAYS A PROBLEM │
│ │
│ Critical path Lazy path │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Entry chunk │ │ Editor chunk │ │
│ │ 500 KB │ │ 500 KB │ │
│ │ │ │ │ │
│ │ Loads on every page │ │ Loads only when │ │
│ │ The warning is a │ │ the editor opens │ │
│ │ real problem │ │ The warning is │ │
│ │ │ │ acceptable │ │
│ └─────────────────────┘ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
The advancedChunks Groups
┌──────────────────────────────────────────────────────────────┐
│ PRIORITY DECIDES WHICH GROUP WINS │
│ │
│ react-vendor (priority 20) │
│ ┌─────────────────────┐ │
│ │ react, react-dom │ The highest priority │
│ └─────────────────────┘ │
│ │
│ charts (priority 15) │
│ ┌─────────────────────┐ │
│ │ recharts, d3 │ The middle priority │
│ └─────────────────────┘ │
│ │
│ vendor (priority 10) │
│ ┌─────────────────────┐ │
│ │ Everything else │ The lowest priority │
│ │ maxSize: 500000 │ Auto-split if too large │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Visualizer | rollup-plugin-visualizer |
| Visualizer options | open, filename, gzipSize, template |
| Chunk warning | build.chunkSizeWarningLimit |
| Vite 8 chunking | advancedChunks with groups |
| Vite 7 chunking | manualChunks function form |
| Critical path | The entry chunk and its static imports |
| Lazy path | The chunks behind dynamic imports |
Key takeaways:
- The visualizer is the composition audit.
rollup-plugin-visualizerproduces a treemap where each module’s area is its size. ThegzipSize: trueoption adds the number that matters for the download. The largest rectangles are the first candidates for replacement or tree-shaking . - The chunk warning is the threshold audit, not the solution.
build.chunkSizeWarningLimitsets the size at which the build warns. Raising the limit hides the warning without reducing the bundle. The correct response is to check whether the chunk is on the critical path and whether it can be split or lazy-loaded . advancedChunksis the Vite 8 chunk configuration. Thegroupsarray declares the split strategy withname,test,priority, and optionalmaxSize. Theprioritycontrols which group wins when a module matches multiple tests, and themaxSizeauto-splits a group that grows too large .- The critical path is the distinction the warning does not make. A 500 KB chunk in the entry is a problem; a 500 KB chunk behind a dynamic import is acceptable. The audit’s job is to check the chunk’s role, not just its size.
- The common findings are the quick wins. A large
moment.js, a wholesalelodashimport, a duplicate React, and a barrel-file icon library are the four findings that appear in most Vite bundle audits. The fixes are replacement, named imports, deduplication, and direct imports .
Remember: The Vite bundle audit is three measurements, not one. The visualizer shows what is in the bundle. The chunk warning shows which chunks exceed the limit. The chunk configuration controls how the bundle is split. The visualizer’s treemap identifies the heavy modules. The warning’s threshold identifies the chunks that need attention. The configuration’s advancedChunks or manualChunks is the treatment. The audit is not complete until the critical path is distinguished from the lazy path, because a large lazy chunk is not a problem, and a large entry chunk is. The tools are the reading; the distinction is the judgment.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!