Library
Learning Patterns · 1 of 12
Learning Patterns
UI/UX Design

bundle optimization case study

Learning Patterns Lydia Hallie & Addy Osmani

Key Principle

Third-party libraries ship features you never use, and those unused features still cost parse time, hydration time, and transfer bytes. The "features used / features shipped" ratio is the primary signal for optimization opportunities. Systematically replacing heavy or feature-bloated dependencies with lighter equivalents (or native APIs) compounds into large aggregate gains because web performance metrics are interdependent: smaller bundles reduce TBT, which unblocks TTI, which lowers LCP perception.

Why This Matters

A single library substitution can account for the largest performance gain in an entire optimization effort. Font Awesome to @svgr/webpack alone improved Speed Index by 34%, LCP by 23%, and TBT by 51%. Across all substitutions in the case study, Lighthouse performance rose from 64% to approximately 96%. The compounding effect is nonlinear because each replacement removes a bottleneck on a different axis (parse time, bundle size, polyfill overhead).

Good Examples

Font Awesome to @svgr/webpack (per-file SVG imports)

  • Scripts: 354.5 KiB down to 152.7 KiB
  • Hydration: 1,004 ms down to 176 ms (82% reduction)
  • Only referenced icons enter the bundle instead of the full icon set

react-slick to react-glider (carousel)

  • Bundle: 14.7 kB gzipped (5 deps) down to 3.4 kB (1 dep) -- 78% savings
  • Zero functional regression
  • Transitive dependencies inflate true cost far beyond the library's own code

react-burger-menu to custom component (sidebar menu)

  • Bundle: 6.73 kB chunk down to 879 B (87% reduction)
  • Parsed size: 32.74 kB down to 2.14 kB
  • The app needed a single off-canvas sidebar; the library supported 12+ animation styles and pulled in snap.svg-cjs

react-select to react-select-search (dropdown)

  • Bundle: 27.2 kB gzipped (7 deps) down to 3.2 kB (0 deps)
  • LCP improved 35%, CLS improved 100%, TBT improved 18%
  • CLS improved because the lighter component renders dimensions synchronously rather than reflowing after style injection

react-rating to react-stars (rendering mechanism)

  • react-rating rendered SVG icons; react-stars uses Unicode character. When repeated 20 times on a page, SVG overhead compounds
  • TBT reduced 33% (100 ms to 66.6 ms)

Counterpoints

Not every library substitution improves performance. Adding react-scroll (6.8 kB) for cross-browser smooth scrolling solved a compatibility gap (native scrollIntoView with smooth behavior had approximately 76% browser support) but caused TBT to spike 296% (16.66 ms to 66 ms) and TTI to worsen 40%. Solving a correctness problem can introduce a performance problem; always measure both dimensions.

Key Quotes

"A reduction in bundle size from 14.7 kB to 3.4 kB was quite a jump (78% improvement) with zero impact on functionality." -- Hallie & Osmani, Learning Patterns, p. 4

Rules of Thumb

  1. Audit bundle size and dependency count using bundlephobia or webpack-bundle-analyzer.
  2. Assess the features-used-to-features-shipped ratio. Low ratio signals a replacement opportunity.
  3. Replace with custom code when feature use is minimal, or a lighter alternative when feature overlap is high.
  4. Measure before and after via Lighthouse (LCP, CLS, TBT, FCP, TTI). Reject changes that regress any metric.
  5. Prefer native APIs as the endgame. CSS Scroll Snap can eliminate carousel libraries entirely. The optimization hierarchy: native API > slim library > full-featured library.
  6. Rendering mechanism matters more than raw bundle size. SVG-based rendering compounds overhead when components repeat on a page; Unicode or CSS alternatives may win even at similar file sizes.
  7. Lazy-load invisible components (collapsed menus, modals) with React.lazy + Suspense. This was the single largest per-technique improvement in the case study: 71% TBT reduction, 31.6% TTI improvement.

Related References

  • Aggregate case study results: Lighthouse 64% to 95.7%, FCP 3.06 s to 0.83 s, LCP 5.0 s to 2.43 s, Speed Index 8.5 s to 3.23 s (Hallie & Osmani, Learning Patterns)
  • CSS scoping: moving component-specific CSS from global _app.js to inline style jsx improved FCP, LCP, and TTI by 2-5%
  • React 18 streaming SSR and selective hydration decouple per-component readiness from whole-page readiness, breaking the slowest-component bottleneck