I worked on a B2B application where I faced a common misconception: businesses are more tolerant of poor Google Core Web Vitals and bloated bundle sizes because "users access from office PCs with fast internet." Unfortunately, reality was different. Primary users accessed the application from mobile phones with mobile internet connections. When I discovered this, optimization became critical.
Starting Point: The production bundle size was 1,542 KB (gzipped).
Target: 500 KB (following industry best practices)
Step 1: Compression - Already Optimized
The obvious first step was enabling Brotli compression, which typically saves 15-25% compared to gzip. However, the infrastructure already had Brotli enabled. No gains here.
Step 2: Route-Based Code Splitting
React Router supports lazy loading out of the box. I eagerly implemented dynamic imports for all routes:
// Before
import Dashboard from './Dashboard';
// After
const Dashboard = React.lazy(() => import('./Dashboard'));
Result: Bundle reduced by 37% to 971 KB. A great start!
Step 3: Dependency Analysis & Vendor Splitting
Using Webpack Bundle Analyzer, I discovered heavy third-party libraries dominating the bundle:
- Date picker/calendar libraries
- Feature flag management
- Analytics SDKs
- PDF editor
- WYSIWYG editor
- File upload widget
These were imported directly across multiple modules. I created wrapper modules with dynamic imports:
// Before - Direct imports everywhere
import { DatePicker } from 'heavy-date-library';
// After - Wrapper with lazy loading
export const loadDatePicker = () =>
import('heavy-date-library').then(module => module.DatePicker);
Result: Bundle reduced to 57% of original size (879 KB).
Step 4: Module Decoupling & Dependency Chains
The bundle analyzer revealed tightly coupled dependency chains. For example:
// ❌ Tightly coupled
// Module A imported Module B, which imported Module C,
// which imported a heavy utility library
// All loaded together even if only one piece was needed
// ✅ Solution: Extract shared code to independent modules
// Created small, focused modules with only necessary shared code
// Broke circular dependencies
I identified and separated these chains, creating smaller, focused modules with only necessary shared code.
Result: Bundle at 63% reduction (571 KB).
Step 5: Localization Optimization
The application supported several languages, but all translations loaded synchronously regardless of user preference.
Before: All language files bundled together (≈ 120 KB of JSON).
After: Only the user's selected language loads initially:
// Dynamic locale loading
const loadLocale = (locale) =>
import(`./locales/${locale}.json`);
// Initial load: only user's language or default
Final Result: 493 KB - a 68% reduction that beat our 500 KB target!
Key Takeaways
- Assumptions are dangerous: I learned never to assume user context without data
- Analyze before optimizing: Webpack Bundle Analyzer was invaluable for my process
- Third-party libraries are expensive: I became strategic about heavy dependencies
- Dependency chains matter: I discovered tight coupling creates bundle bloat
- Localization can be heavy: Dynamic loading per language is essential
Top comments (0)