<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ankita Patil — Engineering Notes]]></title><description><![CDATA[Deep dives into React, Next.js, performance optimization, scalable frontend architecture, and practical engineering lessons from real-world projects.]]></description><link>https://blog.ankitapatil.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 18:46:52 GMT</lastBuildDate><atom:link href="https://blog.ankitapatil.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Breaking Down a 68MB React Build: Architecture Fixes That Cut It to 21MB]]></title><description><![CDATA[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 pulli]]></description><link>https://blog.ankitapatil.dev/react-build-size-optimization</link><guid isPermaLink="true">https://blog.ankitapatil.dev/react-build-size-optimization</guid><category><![CDATA[React]]></category><category><![CDATA[performance]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[bundle-optimization]]></category><category><![CDATA[webpack]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[System Architecture]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Ankita Patil]]></dc:creator><pubDate>Mon, 09 Mar 2026 15:14:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/d033e9f2-98a3-4524-8c7e-0e1d2cd92a51.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Our React production build was 68MB.<br />Not a typo. Sixty. Eight. MB.</p>
<p>Deployments were slow. Uploads were painful. And the easy excuse was:<br />“Big app. Happens.”</p>
<p>Nah.</p>
<p>So I opened the bundle and started pulling it apart.</p>
<p>This post walks through:</p>
<ul>
<li><p>How I analyze a production build when something feels off</p>
</li>
<li><p>What usually hides inside large React bundles</p>
</li>
<li><p>The architectural fixes that brought it down from <strong>68MB to 21MB</strong></p>
</li>
</ul>
<p>Because this isn’t about shaving numbers for fun.<br />It’s about faster deploys, better caching, quicker rollbacks — and not panicking on release day.</p>
<p>If you're shipping frontend to production, this stuff matters.</p>
<hr />
<h2>Step 1 — Quick Win: Kill Source Maps in Production</h2>
<p>First check: production was generating source maps.</p>
<p>Turned that off.</p>
<p><strong>68MB → 25MB instantly.</strong></p>
<p>Good win.<br />But that still left 25MB of actual application code.</p>
<p>So the real question became:</p>
<p><strong>What exactly are we shipping to users?</strong></p>
<hr />
<h2>Step 2 — What Are We Actually Shipping?</h2>
<p>A production bundle is basically a giant, minified blob.</p>
<p>Manually scanning files isn’t realistic.<br />So I generated build stats and ran them through a Webpack bundle analyzer.</p>
<p>The treemap visualization made it very obvious where the weight was coming from.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/f79ab705-c9d5-427c-bc4e-50e10f3b4253.png" alt="Webpack Bundle Analyzer treemap showing Highcharts and other heavy dependencies inside the main chunk" style="display:block;margin:0 auto" />

<p>In a clean, role-based SaaS architecture:</p>
<ul>
<li><p>The main bundle should contain only core app logic</p>
</li>
<li><p>Feature libraries should load with their routes</p>
</li>
<li><p>Heavy libraries (charts, PDF tools, editors, etc.) should not sit in the entry chunk unless truly global</p>
</li>
</ul>
<p>The visualization made one thing clear — that separation wasn’t strict.</p>
<p>And that’s where the real fixes started.</p>
<hr />
<h2>Step 3 — Keep Route-Level Dependencies Out of the Main Bundle</h2>
<p>While reviewing the treemap, one thing stood out.</p>
<p><strong>Highcharts was inside the main bundle.</strong></p>
<p>That didn’t make sense.</p>
<p>Charts were used only inside dashboards.<br />Dashboards were lazy-loaded per role.</p>
<p>After tracing imports, I found this in the root entry file:</p>
<pre><code class="language-javascript">import Highcharts from "highcharts";

Highcharts.setOptions({
  chart: {
    style: {
      fontFamily: "'Poppins', sans-serif",
    },
  },
});
</code></pre>
<p>That one import was enough.</p>
<p>Even though routes were lazy-loaded, the dependency wasn’t.</p>
<p>So every single user — even those who never opened a dashboard — was downloading a charting library.</p>
<h3>Fix</h3>
<p>I moved the chart configuration into a dedicated chart module and imported it only inside chart-related components.</p>
<p>Rebuilt.</p>
<p><strong>25MB → 21MB.</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/caefd33e-c5cb-4f37-92c0-24f43983293d.png" alt="React Webpack Bundle Analyzer treemap after removing Highcharts from main bundle" style="display:block;margin:0 auto" />

<p>More importantly:</p>
<ul>
<li><p>Highcharts was removed from the main bundle</p>
</li>
<li><p>It now loads only when a dashboard loads</p>
</li>
<li><p>Route-based code splitting started behaving correctly</p>
</li>
</ul>
<p>This wasn’t just about shaving 4MB.</p>
<p>It was about making sure users download only what they actually use.</p>
<hr />
<h2>Step 4 — Don’t Ship Two Libraries for One Problem</h2>
<p>During dependency audit, both <code>moment</code> and <code>dayjs</code> were present.</p>
<p>Classic enterprise pattern.</p>
<p>Features get added. Libraries stick around.</p>
<p>But duplicate libraries mean:</p>
<ul>
<li><p>Bigger bundle</p>
</li>
<li><p>More cognitive load</p>
</li>
<li><p>More maintenance surface area</p>
</li>
</ul>
<p>Before removing anything, I evaluated:</p>
<ul>
<li><p>Actual feature usage in the codebase</p>
</li>
<li><p>Edge cases (timezones, parsing, formatting)</p>
</li>
<li><p>Bundle size impact using tools like Bundlephobia</p>
</li>
</ul>
<p><code>dayjs</code> covered our requirements with a smaller footprint.</p>
<p>So I standardized on <code>dayjs</code>, replaced remaining <code>moment</code> usage, and removed it.</p>
<p>Result:</p>
<ul>
<li><p>Leaner dependency graph</p>
</li>
<li><p>Smaller production build</p>
</li>
<li><p>Cleaner architectural boundary</p>
</li>
</ul>
<p>Optimization isn’t always about clever tricks.</p>
<p>Sometimes it’s about deliberate decisions.</p>
<hr />
<h2>Step 5 — Static Assets Matter Too</h2>
<p>JavaScript isn’t the only thing that grows silently.</p>
<p>Over time, the <code>/public</code> folder had accumulated unused images and static assets — old design iterations, deprecated illustrations, leftover icons.</p>
<p>These don’t show up in JS bundle analyzers, but they:</p>
<ul>
<li><p>Increase deployment size</p>
</li>
<li><p>Slow down uploads</p>
</li>
<li><p>Add unnecessary storage and CDN overhead</p>
</li>
</ul>
<p>A quick audit removed unused files and cleaned up the directory structure.</p>
<p>Performance isn’t just about code.</p>
<p>It’s about everything being shipped.</p>
<hr />
<h2>What I Look For in Enterprise Bundle Optimization</h2>
<p>From this experience, these are the patterns I now actively check:</p>
<h3>1. Entry Point Imports</h3>
<p>Anything imported in <code>index.tsx</code> or root files becomes part of the main bundle.</p>
<p>Global configuration imports must be intentional.</p>
<h3>2. Feature Isolation</h3>
<p>Route-based apps should reflect in bundle structure.</p>
<p>If dashboards are lazy-loaded, their heavy dependencies must be lazy-loaded too.</p>
<h3>3. Duplicate Libraries</h3>
<p>Multiple libraries solving the same problem should be evaluated and standardized.</p>
<h3>4. Heavy Libraries</h3>
<p>Charts, PDF tools, editors, icon packs — common bloat sources.</p>
<p>They should be lazy-loaded, dynamically imported, or replaced with lighter alternatives.</p>
<h3>5. Raw Code vs Dependency</h3>
<p>Sometimes small utilities are better implemented in-house instead of importing an entire package.</p>
<p>Dependency discipline matters in enterprise systems.</p>
<hr />
<h2>Final Results</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>Production Build</td>
<td>68MB</td>
<td>21MB</td>
</tr>
<tr>
<td>Minified Main Bundle</td>
<td>529.57 KB</td>
<td>431.56 KB</td>
</tr>
<tr>
<td>Main Bundle Content</td>
<td>Included route-level chart library</td>
<td>Clean core bundle</td>
</tr>
<tr>
<td>Date Libraries</td>
<td>Two</td>
<td>One</td>
</tr>
<tr>
<td>Deployment Time</td>
<td>Slower</td>
<td>Improved</td>
</tr>
</tbody></table>
<hr />
<h2>Key Learnings</h2>
<ul>
<li><p>Bundle size reflects architecture decisions.</p>
</li>
<li><p>Lazy loading works only when dependencies are isolated correctly.</p>
</li>
<li><p>Root-level imports can silently defeat code splitting.</p>
</li>
<li><p>Regular dependency audits are essential in growing codebases.</p>
</li>
<li><p>Optimization isn’t about removing libraries — it’s about structuring intentionally.</p>
</li>
</ul>
<p>This experience changed how I think about performance in React systems.</p>
<p>Instead of optimizing at the end, bundle structure is now treated as part of architecture design from day one.</p>
]]></content:encoded></item></channel></rss>