Arsanyos Asrat Emiru

How TreeShaking unused components and code-splitting improved our WebApp performance by about 2000 ms

Arsanyos Asrat3 min read

The myth of Tree-shaking and code-splitting at the bundler and browser level. A comprehensive guide to optimizing React bundles and reducing load times by 2 seconds.

Originally published on LinkedIn

View original post →

How TreeShaking unused components and code-splitting improved our WebApp performance by about 2000 ms

Are your React bundles getting chunkier by the day? We faced the same issue until we implemented a simple yet powerful optimization strategy. And I'm going to unveil this sacred method to you in a bit, sit tight and enjoy the ride. :D

1. The Problem: Bloated Component Wrappers

The core issue we identified was in how our component wrappers were implemented. The traditional approach of static imports was silently bloating our bundle size, creating a direct trade-off between developer convenience and user experience—especially for those on unreliable connections or lower-tier devices TreeShaking.

An Instance for this look something like this:

import React from 'react';
import ComponentA from '@components/ComponentA';
import ComponentB from '@components/ComponentB';

export default function index({ template, ...props }) {
  if (template == 'ComponentA') return <ComponentA {...props} />;
  return <ComponentB {...props} />;
}

2. The Solution: Dynamic Imports + Code-Splitting

We refined our solution using template literals with dynamic imports, creating a more maintainable and scalable approach. By constructing the import path dynamically based on our theme configuration, we leverage both tree shaking at build time and efficient code splitting at runtime. The bundler analyzes the pattern and eliminates all unused Bar variants, while React's lazy() ensures only the required component loads when needed. This streamlined approach reduces code complexity while maintaining the performance benefits— visible in our bundle analyzer as optimized, tree-shaken chunks with clean separation.

import appThemeConfiguration from '@appConfig';
import React from 'react';

const template = appThemeConfiguration.BAR_VERSION;

const BarComponent = React.lazy(() =>
  import(`./${template}Bar/${template}Bar.jsx`).then((module) => ({
    default: module.default,
  }))
);

export default function BarWrapper({ ...props }) {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <BarComponent {...props} />
    </React.Suspense>
  );
}
BarWrapper.displayName = 'BarWrapper';

3. Optimization Rollup Configuration

To maximize our optimization strategy, we implemented strategic code splitting through Rollup's manual chunk configuration. By categorizing dependencies into logical vendor groups—React, routing, state management—we ensure better caching and faster subsequent visits. While hashed filenames guarantee cache-busting on updates. This granular approach transforms our monolithic bundle into optimized, cache-friendly chunks that load in priority order, dramatically improving initial page load and runtime performance.

rollupOptions: {
  output: {
    // manualChunks,
    // Optimize vendor code splitting for better caching
    manualChunks(id) {
      if (id.includes('node_modules')) {
        if (id.includes('/react/') || id.includes('/react-dom/')) {
          return 'vendor-react';
        }
        // React Router - routing library
        if (id.includes('react-router')) {
          return 'vendor-router';
        }
        // State management - Redux
        if (
          id.includes('@reduxjs/toolkit') ||
          id.includes('react-redux')
        ) {
          return 'vendor-redux';
        }
      }
    },
    entryFileNames: 'assets/[name]-[hash].js',
    chunkFileNames: 'assets/[name]-[hash].js',
    assetFileNames: 'assets/[name]-[hash][extname]',
  },
}

Key Takeaways

  • Dynamic imports with React.lazy() enable true code-splitting at the component level
  • Strategic chunking improves caching and reduces redundant downloads
  • Bundle analysis helps identify optimization opportunities
  • Performance gains of 2000ms directly impact user experience and Core Web Vitals

This optimization strategy transformed our application's performance, reducing bundle sizes and improving load times significantly. The combination of tree-shaking unused components and strategic code-splitting creates a more efficient, user-friendly web application.

A

Arsanyos Asrat

Frontend Engineer | Performance Optimization Specialist