Front-end Engineering Lab
Patterns

Code Splitting Strategies

Optimize bundle size and improve Core Web Vitals with smart code splitting

Code Splitting Strategies

Code splitting is one of the most effective ways to improve your app's performance and Core Web Vitals. Load only what users need, when they need it.

🎯 Why Code Splitting?

Without Code Splitting:
Bundle: 2.5 MB
FCP: 4.2s ❌
LCP: 5.8s ❌
User waits forever...

With Code Splitting:
Initial: 150 KB
FCP: 0.8s ✅
LCP: 1.2s ✅
Rest loads as needed

📊 Impact on Core Web Vitals

MetricWithout SplittingWith SplittingImprovement
FCP4.2s0.8s81% faster
LCP5.8s1.2s79% faster
TTI6.5s1.5s77% faster
Bundle2.5 MB150 KB94% smaller

🔧 Code Splitting Methods

1. Route-Based Splitting (Most Common)

// ❌ BAD: All routes loaded upfront
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';
import Profile from './pages/Profile';

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

// ✅ GOOD: Each route loads on demand
import { lazy, Suspense } from 'react';

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

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

2. Component-Based Splitting

// ❌ BAD: Heavy component always loaded
import RichTextEditor from './RichTextEditor'; // 500 KB!

function BlogPost() {
  const [editing, setEditing] = useState(false);
  
  return (
    <div>
      {editing && <RichTextEditor />}
    </div>
  );
}

// ✅ GOOD: Load only when needed
const RichTextEditor = lazy(() => import('./RichTextEditor'));

function BlogPost() {
  const [editing, setEditing] = useState(false);
  
  return (
    <div>
      {editing && (
        <Suspense fallback={<div>Loading editor...</div>}>
          <RichTextEditor />
        </Suspense>
      )}
    </div>
  );
}

3. Library Splitting (Vendor Chunks)

// next.config.js / vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Vendor chunk for React
          'react-vendor': ['react', 'react-dom'],
          
          // Heavy libraries separate
          'charts': ['chart.js', 'react-chartjs-2'],
          'editor': ['slate', 'slate-react'],
          
          // Utils together
          'utils': ['lodash', 'date-fns']
        }
      }
    }
  }
};

📈 Bundle Analysis

# Analyze your bundle
npm run build
npx source-map-explorer 'dist/*.js'

# Or with webpack
npx webpack-bundle-analyzer stats.json

What to look for:

  • ✅ Main bundle < 200 KB (gzipped)
  • ✅ Vendor chunks < 150 KB each
  • ✅ Route chunks < 100 KB each
  • ⚠️ Any chunk > 500 KB needs splitting

🎯 Code Splitting Checklist

  • Routes are lazy loaded
  • Heavy components split (charts, editors, etc)
  • Vendor libraries in separate chunks
  • Dynamic imports for conditional features
  • Preload critical routes
  • Bundle analyzed and optimized
  • Core Web Vitals tested

📚 Pattern Catalog

This section covers:

🏆 Best Practices

  1. Measure first - Use Lighthouse/WebPageTest
  2. Split at routes - Easiest and most effective
  3. Target 200 KB initial - Gzipped bundle size
  4. Preload critical paths - User likely navigation
  5. Monitor over time - Bundle size grows quickly
  6. Test on 3G - Slow networks expose issues
  7. Use CDN - Faster chunk delivery

🎓 Quick Wins

// 1. Lazy load routes (5 min, huge impact)
const Dashboard = lazy(() => import('./Dashboard'));

// 2. Split heavy libraries (10 min)
const Chart = lazy(() => import('react-chartjs-2'));

// 3. Vendor chunks (webpack.config.js, 5 min)
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /node_modules/,
        name: 'vendor'
      }
    }
  }
}

// Result: 70-80% smaller initial bundle ✅

📊 Real-World Results

Before Code Splitting

Initial Bundle: 2.3 MB
FCP: 4.1s
LCP: 5.9s
Lighthouse Score: 34

After Code Splitting

Initial Bundle: 178 KB
FCP: 0.9s
LCP: 1.3s
Lighthouse Score: 96

Improvement: 92% smaller bundle, 78% faster load time 🚀


Next: Dive into specific splitting strategies to optimize your app for Core Web Vitals.

On this page