Claude
Skills
Sign in
Back

vite-bundle-optimization

Included with Lifetime
$97 forever

Teaches Vite-specific bundle optimization patterns. Use when configuring Vite builds, code splitting, managing dependencies, or troubleshooting slow Vite builds.

General

What this skill does


# Vite Bundle Optimization

## Table of Contents

- [When to Use](#when-to-use)
- [Instructions](#instructions)
- [Details](#details)
- [Source](#source)

Production-ready patterns for optimizing bundle size and build performance in Vite + React applications. These patterns leverage Vite's architecture (native ESM in dev, Rollup in production) to deliver smaller, faster bundles.

## When to Use

Reference these patterns when:
- Setting up a new Vite + React project for production
- Analyzing bundle size with `npx vite-bundle-visualizer`
- Build times are slow or bundles are unexpectedly large
- Migrating from webpack/CRA to Vite
- Optimizing Core Web Vitals (LCP, FID/INP, CLS)

## Instructions

- Apply these patterns during project setup, build configuration, and bundle size reviews. When you see large bundles or slow builds, diagnose with `npx vite-bundle-visualizer` and apply the relevant pattern.

## Details

### Overview

Vite uses esbuild for dependency pre-bundling and development transforms, and Rollup for production builds. Understanding this dual architecture is key to optimizing effectively. The patterns below are ordered by impact.

---

### 1. Avoid Barrel File Imports

**Impact: CRITICAL** — Can add 200-800ms to startup and 2-4s to dev server boot.

Barrel files (`index.ts` that re-export from many modules) force bundlers to load the entire module graph even when you only use one export. This is the #1 bundle size issue in React apps.

**Avoid — imports entire library through barrel:**

```tsx
import { Button, TextField } from '@/components'
// Loads ALL components in the barrel, even unused ones

import { Check, X, Menu } from 'lucide-react'
// Loads all 1,500+ icons (~2.8s in dev)
```

**Prefer — direct imports:**

```tsx
import { Button } from '@/components/Button'
import { TextField } from '@/components/TextField'

import Check from 'lucide-react/dist/esm/icons/check'
import X from 'lucide-react/dist/esm/icons/x'
import Menu from 'lucide-react/dist/esm/icons/menu'
```

**Auto-fix with `vite-plugin-barrel`:**

```typescript
// vite.config.ts
import barrel from 'vite-plugin-barrel'

export default defineConfig({
  plugins: [
    react(),
    barrel({
      packages: ['lucide-react', '@mui/material', '@mui/icons-material'],
    }),
  ],
})
```

This transforms barrel imports into direct imports at build time, giving you ergonomic syntax with direct-import performance.

**Commonly affected libraries:** `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@radix-ui/react-*`, `lodash`, `date-fns`, `rxjs`.

---

### 2. Configure Manual Chunk Splitting

**Impact: HIGH** — Better caching, parallel loading, smaller initial bundle.

Vite's default chunking puts all vendor code into one file. Split it so that frequently-changing app code doesn't invalidate the vendor cache.

```typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Core React — rarely changes
          'vendor-react': ['react', 'react-dom'],
          // Router — changes infrequently
          'vendor-router': ['react-router-dom'],
          // Data layer — changes occasionally
          'vendor-query': ['@tanstack/react-query'],
          // UI framework — changes with design updates
          'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
        },
      },
    },
  },
})
```

For more dynamic splitting based on module paths:

```typescript
manualChunks(id) {
  if (id.includes('node_modules')) {
    if (id.includes('react-dom')) return 'vendor-react'
    if (id.includes('react-router')) return 'vendor-router'
    if (id.includes('@tanstack')) return 'vendor-query'
    return 'vendor' // everything else
  }
},
```

---

### 3. Dynamic Imports for Route-Level Code Splitting

**Impact: HIGH** — Load only the code needed for the current page.

Use `React.lazy()` with dynamic imports to split each route into its own chunk.

```tsx
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageSkeleton />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}
```

Vite automatically creates separate chunks for each lazy import. Name them for easier debugging:

```tsx
const Dashboard = lazy(() =>
  import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
)
```

---

### 4. Lazy-Load Heavy Components Below the Fold

**Impact: HIGH** — Reduces initial bundle for faster LCP.

Components that aren't visible on initial load (modals, charts, editors, maps) should be lazy-loaded.

```tsx
import { lazy, Suspense, useState } from 'react'

const RichTextEditor = lazy(() => import('./components/RichTextEditor'))
const ChartPanel = lazy(() => import('./components/ChartPanel'))

function ArticlePage() {
  const [editing, setEditing] = useState(false)

  return (
    <article>
      <h1>Article Title</h1>
      <p>Content visible immediately...</p>

      {editing && (
        <Suspense fallback={<EditorSkeleton />}>
          <RichTextEditor />
        </Suspense>
      )}

      <Suspense fallback={<ChartSkeleton />}>
        <ChartPanel />
      </Suspense>
    </article>
  )
}
```

---

### 5. Defer Third-Party Scripts

**Impact: HIGH** — Analytics, tracking, and widgets shouldn't block rendering.

Load non-critical third-party scripts after the page is interactive.

**Avoid — blocks initial render:**

```tsx
// main.tsx
import * as Sentry from '@sentry/react'
import posthog from 'posthog-js'

Sentry.init({ dsn: '...' })
posthog.init('...')
```

**Prefer — load after hydration/mount:**

```tsx
// main.tsx — defer to idle time
function initThirdParty() {
  import('@sentry/react').then(Sentry => {
    Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN })
  })
  import('posthog-js').then(({ default: posthog }) => {
    posthog.init(import.meta.env.VITE_POSTHOG_KEY)
  })
}

if ('requestIdleCallback' in window) {
  requestIdleCallback(initThirdParty)
} else {
  setTimeout(initThirdParty, 2000)
}
```

For external script tags, use `defer` or dynamically inject them:

```typescript
function loadScript(src: string) {
  const script = document.createElement('script')
  script.src = src
  script.async = true
  document.body.appendChild(script)
}
```

---

### 6. Preload Critical Assets on User Intent

**Impact: MEDIUM** — Eliminates perceived latency on navigation.

Start loading a route's code when the user signals intent (hover, focus) rather than waiting for the click.

```tsx
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
  const preload = () => {
    // Vite creates a module preload for dynamic imports
    switch (to) {
      case '/dashboard':
        import('./pages/Dashboard')
        break
      case '/settings':
        import('./pages/Settings')
        break
    }
  }

  return (
    <Link to={to} onMouseEnter={preload} onFocus={preload}>
      {children}
    </Link>
  )
}
```

For `<link rel="modulepreload">` in the HTML head:

```html
<!-- Preload critical route chunks -->
<link rel="modulepreload" href="/assets/Home-abc123.js" />
```

Vite automatically adds `<link rel="modulepreload">` for entry chunks. Add manual preloads for routes you know users will visit next.

---

### 7. Configure Dependency Pre-Bundling

**Impact: MEDIUM** — Faster dev server startup and page loads.

Vite pre-bundles `node_modules` dependencies u

Related in General