| | |

Vite 29 ⚡ Performance Auditing — Bundle analysis & chunk sizing

⏱️ Estimated read: 13 min | |

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

OptionPurpose
openOpen the report after the build
filenameThe report output path
gzipSizeInclude the gzipped size
brotliSizeInclude the brotli size
templatetreemap, sunburst, network, raw-data

The chunk warning

OptionDefaultPurpose
build.chunkSizeWarningLimit500The warning threshold in KB

The chunk configuration

Vite versionMechanismLocation
Vite 8advancedChunksbuild.rolldownOptions.output
Vite 7manualChunksbuild.rollupOptions.output

The common findings

FindingFix
Large moment.jsReplace with date-fns
Large lodashUse lodash-es with named imports
Duplicate Reactresolve.dedupe: ['react', 'react-dom']
Large icon libraryDirect 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

PitfallWhy It HappensFix
The visualizer shows no dataThe plugin is not in the plugins arrayAdd the visualizer() call
The warning does not distinguish lazy chunksThe warning is size-onlyCheck the chunk’s role manually
The vendor chunk is too largeA catch-all groupingSplit the heavy libraries into their own groups
The gzip size is not showngzipSize: falseSet 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

ItemValue
Visualizerrollup-plugin-visualizer
Visualizer optionsopen, filename, gzipSize, template
Chunk warningbuild.chunkSizeWarningLimit
Vite 8 chunkingadvancedChunks with groups
Vite 7 chunkingmanualChunks function form
Critical pathThe entry chunk and its static imports
Lazy pathThe chunks behind dynamic imports

Key takeaways:

  • The visualizer is the composition audit. rollup-plugin-visualizer produces a treemap where each module’s area is its size. The gzipSize: true option 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.chunkSizeWarningLimit sets 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 .
  • advancedChunks is the Vite 8 chunk configuration. The groups array declares the split strategy with name, test, priority, and optional maxSize. The priority controls which group wins when a module matches multiple tests, and the maxSize auto-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 wholesale lodash import, 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.

Was this helpful?


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!