Skip to main content
Technology & EngineeringPerformance Optimization151 lines

Code Splitting

Reduce initial load time with route-based and component-level code splitting using dynamic imports and framework-specific patterns.

Quick Summary18 lines
You are an expert in code splitting and dynamic imports for optimizing application performance.

## Key Points

- **Entry points:** Each entry point produces a separate bundle. Multi-page apps naturally split by page.
- **Dynamic imports:** `import('./module')` tells the bundler to create a separate chunk loaded at runtime. This is the foundation of all code-splitting strategies.
- **Route-based splitting:** The highest-impact pattern — each route loads its own chunk. Users only download code for the page they visit.
- **Component-level splitting:** Heavy components (charts, editors, modals) are loaded only when rendered.
- **Chunk naming:** Use magic comments (`webpackChunkName`) or Rollup's `manualChunks` to control chunk filenames for debugging and caching.
- **Coverage tab (DevTools):** Shows how much downloaded JS/CSS is actually used on the current page. Unused bytes are candidates for splitting.
- **Network waterfall:** After splitting, verify that initial chunks are small and additional chunks load on interaction/navigation.
- **Lighthouse TTI / TBT:** Track these metrics before and after splitting. Expect 20-50% improvement on pages with large bundles.
- **Chunk size monitoring:** Add `size-limit` checks per chunk in CI.
- Start with route-based splitting — it delivers the highest impact with the least refactoring effort.
- Use `webpackPrefetch` or `<link rel="prefetch">` to preload chunks the user is likely to need next, eliminating perceived latency on navigation.
- Keep the main entry chunk under 50KB compressed by aggressively splitting vendor and feature code.
skilldb get performance-optimization-skills/Code SplittingFull skill: 151 lines
Paste into your CLAUDE.md or agent config

Code Splitting — Performance Optimization

You are an expert in code splitting and dynamic imports for optimizing application performance.

Core Philosophy

Overview

Code splitting breaks a monolithic JavaScript bundle into smaller chunks that are loaded on demand. Instead of forcing users to download the entire application upfront, only the code needed for the current view is loaded initially, with additional chunks fetched as users navigate. This directly reduces Time to Interactive (TTI) and Total Blocking Time (TBT).

Core Concepts

  • Entry points: Each entry point produces a separate bundle. Multi-page apps naturally split by page.
  • Dynamic imports: import('./module') tells the bundler to create a separate chunk loaded at runtime. This is the foundation of all code-splitting strategies.
  • Route-based splitting: The highest-impact pattern — each route loads its own chunk. Users only download code for the page they visit.
  • Component-level splitting: Heavy components (charts, editors, modals) are loaded only when rendered.
  • Chunk naming: Use magic comments (webpackChunkName) or Rollup's manualChunks to control chunk filenames for debugging and caching.

Implementation Patterns

React — Route-Based Splitting with Lazy

Before:

import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/settings" element={<Settings />} />
    </Routes>
  );
}

After:

import { lazy, Suspense } from 'react';

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

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

Prefetching Likely Next Routes

// Prefetch dashboard chunk when user hovers over the link
function NavLink({ to, children }) {
  const prefetch = () => {
    if (to === '/dashboard') {
      import(/* webpackPrefetch: true */ './pages/Dashboard');
    }
  };

  return (
    <Link to={to} onMouseEnter={prefetch}>
      {children}
    </Link>
  );
}

Component-Level Splitting for Heavy Libraries

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

function AnalyticsPage({ showChart }) {
  return (
    <div>
      <h1>Analytics</h1>
      {showChart && (
        <Suspense fallback={<Skeleton height={400} />}>
          <ChartPanel />
        </Suspense>
      )}
    </div>
  );
}

Vite / Rollup Manual Chunks

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vendor-react': ['react', 'react-dom'],
          'vendor-charts': ['chart.js', 'react-chartjs-2'],
        },
      },
    },
  },
};

Measurement & Monitoring

  • Coverage tab (DevTools): Shows how much downloaded JS/CSS is actually used on the current page. Unused bytes are candidates for splitting.
  • Network waterfall: After splitting, verify that initial chunks are small and additional chunks load on interaction/navigation.
  • Lighthouse TTI / TBT: Track these metrics before and after splitting. Expect 20-50% improvement on pages with large bundles.
  • Chunk size monitoring: Add size-limit checks per chunk in CI.

Best Practices

  • Start with route-based splitting — it delivers the highest impact with the least refactoring effort.
  • Use webpackPrefetch or <link rel="prefetch"> to preload chunks the user is likely to need next, eliminating perceived latency on navigation.
  • Keep the main entry chunk under 50KB compressed by aggressively splitting vendor and feature code.

Common Pitfalls

  • Creating too many tiny chunks (< 5KB each), which increases HTTP overhead and can hurt performance due to request scheduling latency.
  • Forgetting to add a meaningful Suspense fallback, causing the UI to flash blank while chunks load.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add performance-optimization-skills

Get CLI access →