# Breaking Down a 68MB React Build: Architecture Fixes That Cut It to 21MB

Our React production build was 68MB.  
Not a typo. Sixty. Eight. MB.

Deployments were slow. Uploads were painful. And the easy excuse was:  
“Big app. Happens.”

Nah.

So I opened the bundle and started pulling it apart.

This post walks through:

*   How I analyze a production build when something feels off
    
*   What usually hides inside large React bundles
    
*   The architectural fixes that brought it down from **68MB to 21MB**
    

Because this isn’t about shaving numbers for fun.  
It’s about faster deploys, better caching, quicker rollbacks — and not panicking on release day.

If you're shipping frontend to production, this stuff matters.

* * *

## Step 1 — Quick Win: Kill Source Maps in Production

First check: production was generating source maps.

Turned that off.

**68MB → 25MB instantly.**

Good win.  
But that still left 25MB of actual application code.

So the real question became:

**What exactly are we shipping to users?**

* * *

## Step 2 — What Are We Actually Shipping?

A production bundle is basically a giant, minified blob.

Manually scanning files isn’t realistic.  
So I generated build stats and ran them through a Webpack bundle analyzer.

The treemap visualization made it very obvious where the weight was coming from.

![Webpack Bundle Analyzer treemap showing Highcharts and other heavy dependencies inside the main chunk](https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/f79ab705-c9d5-427c-bc4e-50e10f3b4253.png align="center")

In a clean, role-based SaaS architecture:

*   The main bundle should contain only core app logic
    
*   Feature libraries should load with their routes
    
*   Heavy libraries (charts, PDF tools, editors, etc.) should not sit in the entry chunk unless truly global
    

The visualization made one thing clear — that separation wasn’t strict.

And that’s where the real fixes started.

* * *

## Step 3 — Keep Route-Level Dependencies Out of the Main Bundle

While reviewing the treemap, one thing stood out.

**Highcharts was inside the main bundle.**

That didn’t make sense.

Charts were used only inside dashboards.  
Dashboards were lazy-loaded per role.

After tracing imports, I found this in the root entry file:

```javascript
import Highcharts from "highcharts";

Highcharts.setOptions({
  chart: {
    style: {
      fontFamily: "'Poppins', sans-serif",
    },
  },
});
```

That one import was enough.

Even though routes were lazy-loaded, the dependency wasn’t.

So every single user — even those who never opened a dashboard — was downloading a charting library.

### Fix

I moved the chart configuration into a dedicated chart module and imported it only inside chart-related components.

Rebuilt.

**25MB → 21MB.**

![React Webpack Bundle Analyzer treemap after removing Highcharts from main bundle](https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/caefd33e-c5cb-4f37-92c0-24f43983293d.png align="center")

More importantly:

*   Highcharts was removed from the main bundle
    
*   It now loads only when a dashboard loads
    
*   Route-based code splitting started behaving correctly
    

This wasn’t just about shaving 4MB.

It was about making sure users download only what they actually use.

* * *

## Step 4 — Don’t Ship Two Libraries for One Problem

During dependency audit, both `moment` and `dayjs` were present.

Classic enterprise pattern.

Features get added. Libraries stick around.

But duplicate libraries mean:

*   Bigger bundle
    
*   More cognitive load
    
*   More maintenance surface area
    

Before removing anything, I evaluated:

*   Actual feature usage in the codebase
    
*   Edge cases (timezones, parsing, formatting)
    
*   Bundle size impact using tools like Bundlephobia
    

`dayjs` covered our requirements with a smaller footprint.

So I standardized on `dayjs`, replaced remaining `moment` usage, and removed it.

Result:

*   Leaner dependency graph
    
*   Smaller production build
    
*   Cleaner architectural boundary
    

Optimization isn’t always about clever tricks.

Sometimes it’s about deliberate decisions.

* * *

## Step 5 — Static Assets Matter Too

JavaScript isn’t the only thing that grows silently.

Over time, the `/public` folder had accumulated unused images and static assets — old design iterations, deprecated illustrations, leftover icons.

These don’t show up in JS bundle analyzers, but they:

*   Increase deployment size
    
*   Slow down uploads
    
*   Add unnecessary storage and CDN overhead
    

A quick audit removed unused files and cleaned up the directory structure.

Performance isn’t just about code.

It’s about everything being shipped.

* * *

## What I Look For in Enterprise Bundle Optimization

From this experience, these are the patterns I now actively check:

### 1\. Entry Point Imports

Anything imported in `index.tsx` or root files becomes part of the main bundle.

Global configuration imports must be intentional.

### 2\. Feature Isolation

Route-based apps should reflect in bundle structure.

If dashboards are lazy-loaded, their heavy dependencies must be lazy-loaded too.

### 3\. Duplicate Libraries

Multiple libraries solving the same problem should be evaluated and standardized.

### 4\. Heavy Libraries

Charts, PDF tools, editors, icon packs — common bloat sources.

They should be lazy-loaded, dynamically imported, or replaced with lighter alternatives.

### 5\. Raw Code vs Dependency

Sometimes small utilities are better implemented in-house instead of importing an entire package.

Dependency discipline matters in enterprise systems.

* * *

## Final Results

| Metric | Before | After |
| --- | --- | --- |
| Production Build | 68MB | 21MB |
| Minified Main Bundle | 529.57 KB | 431.56 KB |
| Main Bundle Content | Included route-level chart library | Clean core bundle |
| Date Libraries | Two | One |
| Deployment Time | Slower | Improved |

* * *

## Key Learnings

*   Bundle size reflects architecture decisions.
    
*   Lazy loading works only when dependencies are isolated correctly.
    
*   Root-level imports can silently defeat code splitting.
    
*   Regular dependency audits are essential in growing codebases.
    
*   Optimization isn’t about removing libraries — it’s about structuring intentionally.
    

This experience changed how I think about performance in React systems.

Instead of optimizing at the end, bundle structure is now treated as part of architecture design from day one.
