Framework Guide

Shrink React GPT Bundle: Code-Split & Tree-Shake

Massive bundle sizes from unused imports; we tree-shook dependencies and cut bundle 60%.

January 15, 2025 7 min read

The problem

A React dashboard was shipping 8.4MB of JavaScript to users, causing 15-second load times on mobile networks. Chrome DevTools showed the main bundle at 8.4MB uncompressed, with 2.1MB gzipped. Lighthouse performance score was 12/100. The app imported entire libraries for single functions and loaded every possible component upfront. Mobile users experienced a 83% bounce rate due to the massive download.

How AI created this issue

Developers had been asking ChatGPT for React component examples. ChatGPT consistently imported entire libraries without considering bundle impact:


// ChatGPT's typical imports - bringing in everything
import * as _ from 'lodash';
import moment from 'moment';
import * as Icons from '@mui/icons-material';
import { Chart } from 'chart.js';
import * as d3 from 'd3';
import * as antd from 'antd';
import 'antd/dist/antd.css'; // 60KB just for styles

const Dashboard = () => {
  // Using one function from 600KB lodash
  const sortedData = _.orderBy(data, ['date'], ['desc']);
  
  // Using one icon from 5MB icon set
  const icon = ;
  
  // Importing all of moment.js for simple date formatting
  const formatted = moment(date).format('MMM DD');
  
  // Loading all chart types when using only line charts
  const chart = new Chart(ctx, { type: 'line', data });
  
  // Every possible component loaded upfront
  return (
    
{/* User might never visit most of these */}
); };

ChatGPT never mentioned bundle size implications, tree shaking, or code splitting. It imported entire libraries for convenience, treating bundle size as irrelevant. The AI's examples worked perfectly in development but created a terrible user experience in production.

The solution

  1. Bundle analysis and targeted imports: Identified and fixed wasteful imports:
    
    // Before: 600KB for one function
    import * as _ from 'lodash';
    const sorted = _.orderBy(data, ['date'], ['desc']);
    
    // After: 2KB for just what we need
    import orderBy from 'lodash/orderBy';
    const sorted = orderBy(data, ['date'], ['desc']);
    
    // Or native alternative: 0KB
    const sorted = [...data].sort((a, b) => 
      new Date(b.date) - new Date(a.date)
    );
    
    // Icon optimization
    // Before: 5MB of icons
    import * as Icons from '@mui/icons-material';
    
    // After: 3KB for specific icon
    import DashboardIcon from '@mui/icons-material/Dashboard';
    
    // Date handling
    // Before: 67KB moment.js
    import moment from 'moment';
    
    // After: 2KB date-fns function
    import { format } from 'date-fns';
    const formatted = format(date, 'MMM dd');
  2. Route-based code splitting: Lazy load components based on routes:
    
    // Optimized with React.lazy and Suspense
    import { lazy, Suspense } from 'react';
    import { Routes, Route } from 'react-router-dom';
    
    // Only load components when needed
    const UserDashboard = lazy(() => import('./UserDashboard'));
    const AdminPanel = lazy(() => import('./AdminPanel'));
    const Analytics = lazy(() => import('./Analytics'));
    const Reports = lazy(() => import('./Reports'));
    
    const App = () => {
      return (
        }>
          
            } />
            } />
            } />
            } />
          
        
      );
    };
  3. Webpack optimization: Configured proper tree shaking and splitting:
    
    // webpack.config.js optimizations
    module.exports = {
      optimization: {
        usedExports: true,
        sideEffects: false,
        splitChunks: {
          chunks: 'all',
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name: 'vendors',
              priority: 10,
            },
            common: {
              minChunks: 2,
              priority: 5,
              reuseExistingChunk: true,
            },
          },
        },
      },
      // Analyze bundle
      plugins: [
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          openAnalyzer: false,
        }),
      ],
    };
  4. Component-level code splitting: Split heavy components that aren't always used
  5. Image optimization: Lazy loaded images and used next-gen formats

The results

  • Bundle size reduced from 8.4MB to 1.8MB (79% reduction)
  • Initial load: 2.1MB → 340KB (84% smaller)
  • Time to Interactive: 15s → 2.8s on 3G
  • Lighthouse score: 12 → 94
  • Mobile bounce rate: 83% → 24%
  • Load time on slow 3G: 45s → 7s

The team learned that AI examples optimize for clarity, not production performance. They now review all imports for bundle impact, use bundle analyzers religiously, and prefer native solutions over large libraries. Code that looks clean in development can be a disaster for users on real networks.

Ready to fix your codebase?

Let us analyze your application and resolve these issues before they impact your users.

Get Diagnostic Assessment →