<?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>Tue, 19 May 2026 21:23:21 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[Moving the Brains to the Metal: My Local AI Setup with Gemma 4]]></title><description><![CDATA[Building for the web in 2026 means being an AI orchestrator. Most devs are just API consumers, but if you want to understand the stack, you move the intelligence to the metal. I recently migrated my w]]></description><link>https://blog.ankitapatil.dev/moving-the-brains-to-the-metal-my-local-ai-setup-with-gemma-4</link><guid isPermaLink="true">https://blog.ankitapatil.dev/moving-the-brains-to-the-metal-my-local-ai-setup-with-gemma-4</guid><category><![CDATA[gemma-4]]></category><category><![CDATA[Local LLM]]></category><category><![CDATA[ollama]]></category><category><![CDATA[gen ai]]></category><category><![CDATA[AI Engineering]]></category><category><![CDATA[AI Development Services]]></category><dc:creator><![CDATA[Ankita Patil]]></dc:creator><pubDate>Mon, 04 May 2026 13:15:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/9b0da8a2-75f0-465b-a958-f7354bd39231.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building for the web in 2026 means being an AI orchestrator. Most devs are just API consumers, but if you want to understand the stack, you move the intelligence to the metal. I recently migrated my workflow from cloud dependencies to a local-first setup.</p>
<p>Here is the technical breakdown of how I’m running <strong>Gemma 4</strong> on a constrained system to own my entire development lifecycle.</p>
<h3>The Stack: Why Gemma 4?</h3>
<p>I chose <strong>Gemma 4</strong> as the primary driver for its logic-to-weight ratio. While <strong>Qwen 3</strong> is a solid backup for multilingual reasoning, Gemma 4 hits the sweet spot for React/Next.js code generation and complex system architecture.</p>
<ul>
<li><p><strong>Privacy:</strong> Zero data leakage.</p>
</li>
<li><p><strong>Latency:</strong> Sub-millisecond response times.</p>
</li>
<li><p><strong>Context:</strong> No more worrying about token costs while debugging heavy repos.</p>
</li>
</ul>
<h3>Hardware vs. Tooling: Ollama or LM Studio?</h3>
<p>The choice of engine depends entirely on your system's "personality."</p>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Tool</strong></p></td><td><p><strong>Best For</strong></p></td><td><p><strong>Technical Edge</strong></p></td></tr><tr><td><p><strong>Ollama</strong></p></td><td><p>Terminal-centric devs</p></td><td><p>Lightweight, headless service. Best for 8GB RAM setups.</p></td></tr><tr><td><p><strong>LM Studio</strong></p></td><td><p>GPU-heavy machines</p></td><td><p>High-performance NVIDIA/CUDA offloading. Visual VRAM monitoring.</p></td></tr></tbody></table>

<p>Since I prioritize a minimalist, CLI-driven workflow, I went with <strong>Ollama</strong>. It keeps my system resources lean while serving the model as a local endpoint.</p>
<h3>The Workflow: Aider + Local LLM</h3>
<p>Aider is the bridge that makes local AI feel like a superpower. It’s an autonomous terminal tool that treats your local model like a senior pair programmer.</p>
<ol>
<li><p><strong>Orchestration:</strong> Point Aider to your local Ollama port.</p>
</li>
<li><p><strong>Execution:</strong> Ask for feature updates directly in the terminal.</p>
</li>
<li><p><strong>Result:</strong> Gemma 4 processes the intent, and Aider applies the git-aware edits to your code.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/802eefc8-6295-4c6c-b7e6-e910713fe604.png" alt="Technical diagram of a local AI coding workflow using Ollama and Aider, showing hardware orchestration of the Gemma 4 model interacting with VS Code files." style="display:block;margin:0 auto" />

<h3>The 8GB RAM Constraint: Quantization is Key</h3>
<p>Running a 2026-tier model on 8GB of RAM is an exercise in optimization. To keep the machine from swapping to disk, I used <strong>4-bit quantization</strong>. This reduces the memory footprint by over 60% with negligible loss in coding accuracy.</p>
<p>The Technical Implementation</p>
<p><strong>Fire up the Engine (Ollama)</strong></p>
<p>Once the app is installed, pull the specific model version. I suggest starting with the 4-bit quantized version to save your RAM.</p>
<p>PowerShell</p>
<p>#<code>Pull the model to your local library</code></p>
<p><code>ollama pull gemma4:4b</code></p>
<p>#<code>Run and verify the model is active</code></p>
<p><code>ollama run gemma4:4b</code></p>
<p><strong>The Aider Integration</strong></p>
<p>Aider needs to know where your local "brain" is living. Since Ollama serves models on port <code>11434</code> by default, we point Aider there.</p>
<p>PowerShell</p>
<p>#<code>Install Aider in your project environment</code></p>
<p><code>pip install aider-chat</code></p>
<p>#<code>Launch Aider pointing to your local Gemma instance</code></p>
<p><code>aider --model ollama/gemma4</code></p>
<p><strong>Memory Management</strong> <strong>(The 8GB Survival Script)</strong></p>
<p>If your system is struggling, you can check exactly what is eating your memory before you start. I use a quick one-liner to find and kill heavy processes that aren't needed for the current build.</p>
<p>PowerShell</p>
<p>#<code>Check for the top 5 memory hogs</code></p>
<p><code>ps | sort –p ws | select –last 5</code></p>
<p><strong>Pro-tip:</strong> Kill your Docker containers and heavy browser caches before starting a long inference session. Every megabyte counts when you're running the brain and the dev server on the same chip.</p>
]]></content:encoded></item><item><title><![CDATA[Stop Building Digital Graveyards: Solving the AI Latency Tax with Shadow Ingestion. ]]></title><description><![CDATA[Most fashion apps fail, because the friction of data entry is higher than the quality of output. You download the app, spend 10 minutes manually tagging the shirt, then remember you have 50 more items]]></description><link>https://blog.ankitapatil.dev/ai-orchestration-shadow-ingestion</link><guid isPermaLink="true">https://blog.ankitapatil.dev/ai-orchestration-shadow-ingestion</guid><category><![CDATA[Product Engineering]]></category><category><![CDATA[AI Orchestration]]></category><category><![CDATA[n8n]]></category><category><![CDATA[gemini]]></category><category><![CDATA[llm]]></category><category><![CDATA[openai]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[fashion tech]]></category><dc:creator><![CDATA[Ankita Patil]]></dc:creator><pubDate>Thu, 23 Apr 2026 14:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/316567b8-d9ed-4c62-9958-93915a9f01ea.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most fashion apps fail, because the friction of data entry is higher than the quality of output. You download the app, spend 10 minutes manually tagging the shirt, then remember you have 50 more items to go, and then eventually delete the app.</p>
<p>I have been working on a project that will match the vibe and not the stress. As a product engineer I set one rule for <strong>VibeFit</strong>: if user have to type "Yellow", "Cotton", or "Summer" , I've failed. In 2026 we don't build CRUD apps for users to deal with same stress on screen that they already bear off screen, instead we build <strong>intent-based engines.</strong></p>
<hr />
<p><strong>Orchestrating the invisible:</strong></p>
<p>Most devs go for monolith Express server to handle AI logic. But in AI driven product, your backend is the logic glue, it's not just a gatekeeper for a DB.</p>
<p>I chose <strong>n8n orchestration</strong>, and here's the engineering reality:</p>
<ul>
<li><p><strong>Observability over black box code</strong>: We've been developing the apps by checking logs and debuggers, and it do take time. When Gemini return a JSON object wrapped in markdown, I don't want to go through the logs, I want to see exactly where the pipe leaked on a visual canvas.</p>
</li>
<li><p><strong>Decoupling the Stylist</strong>: My Next.Js frontend should not care about how Gemini-2.5 Flash tags a photo. So I offloaded this to keep the <strong>Main Thread Lean</strong>, focusing on premium UX.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/0e46ac00-f64c-41c3-b001-2393a51b0b40.png" alt="A technical architecture diagram of the VibeFit ingestion pipeline. It shows an image upload from a Next.js frontend triggering an n8n orchestration workflow. The flow : visual tagging via Gemini-2.5 Flash and 1536-dimension vector embedding generation via OpenAI, finally syncing to a Supabase PostgreSQL database via a real-time subscription." style="display:block;margin:0 auto" />

<p>fig: The VibeFit Ingestion Pipeline: Turning raw pixels into structured semantic data.</p>
<hr />
<p><strong>The classic Latency Tax:</strong><br />Most AI products break (right at the UX), when you upload a photo, and the app shows a spinner for 8 seconds while the AI is thinking. For VibeFit I decided to treat this AI Thinking as a background task, and named it <strong>Shadow Ingestion</strong>.</p>
<p>How exactly Shadow Ingestion works:</p>
<ol>
<li><p><strong>The handshake</strong>: User uploads the image and UI returns a "Success" state in &lt;200ms.</p>
</li>
<li><p><strong>The Background task</strong>: An n8n webhook takes the image, hits Gemini 2.5 Flash for visual tagging, and OpenAI for a 1536-dimension embedding.</p>
</li>
<li><p><strong>The Reveal</strong>: As soon as background processing is done, the database updates, and the item is shown into the wardrobe via a Supabase real-time subscription.</p>
</li>
</ol>
<p>The user thinks its magic. The reality is just <strong>asynchronous orchestration hygiene</strong>.</p>
<hr />
<p><strong>The bottom line</strong>: AI product is only as good as its data glue. With manual ingestion, your app is already dead, no matter how better the ui/ux is. By automating the extraction of vibes and not just categories, I've built the foundation for a true RAG experience.</p>
<p>Next in the series: <em>The Math of Style: Why I chose pgvector for 1536-dimension similarity matching.</em></p>
]]></content:encoded></item><item><title><![CDATA[Your AI Stream is Fast, but Your UI is Lagging. Here’s Why.]]></title><description><![CDATA[We’ve all seen it. You’re building a sleek AI-chat interface or a real-time agent dashboard. The tokens are flying in from the server at light speed, but the moment you try to scroll or click a "Stop"]]></description><link>https://blog.ankitapatil.dev/main-thread-hygiene-javascript-performance</link><guid isPermaLink="true">https://blog.ankitapatil.dev/main-thread-hygiene-javascript-performance</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[performance]]></category><category><![CDATA[#AIOptimization]]></category><category><![CDATA[genai]]></category><category><![CDATA[Core Web Vitals]]></category><dc:creator><![CDATA[Ankita Patil]]></dc:creator><pubDate>Sat, 18 Apr 2026 09:00:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/b4bd7404-a6f0-499c-9d81-72f1682bc987.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We’ve all seen it. You’re building a sleek AI-chat interface or a real-time agent dashboard. The tokens are flying in from the server at light speed, but the moment you try to scroll or click a "Stop" button, the tab feels like it's stuck in mud.</p>
<p>Most devs look at their React code or their Tailwind classes. They’re looking in the wrong place.</p>
<p>The problem isn't your UI library. The problem is that you’re <strong>Microtask-smothering</strong> your browser.</p>
<h2>The 2026 Conundrum: The "Infinite" VIP Line</h2>
<p>In the "classic" web, we had a few promises here and there. In 2026, we have <strong>Streams</strong>.</p>
<p>Every time your AI agent emits a token, or your local vector DB returns a similarity match, you’re likely firing a <code>Promise.resolve()</code> or an <code>await</code>. In JavaScript land, these are <strong>Microtasks</strong>.</p>
<p>Here’s the dirty secret: The Event Loop is a pushover. It sees a Microtask and says, <em>"Come right in, VIP!"</em> The problem? If your AI is streaming 60 tokens per second and you’re updating state for every single one, that "VIP line" never ends.</p>
<p>The browser wants to paint the next frame (the Macrotask). It’s literally begging to show the user the scroll animation. But the Event Loop says, <em>"Sorry, I have 400 more microtasks to finish first."</em></p>
<img src="https://cdn.hashnode.com/uploads/covers/69916e90a0e1de594226e1d0/84c6e0c8-7937-44ff-bdc0-108907a694ee.png" alt="diagram of the JavaScript Event Loop comparing a &quot;UI Rendering&quot; macrotask waiting in line while a stream of &quot;AI Token&quot; microtasks zoom past a bouncer." style="display:block;margin:0 auto" />

<h2>Why <code>setTimeout</code> is Obsolete</h2>
<p>For years, we "fixed" this by throwing a <code>setTimeout(fn, 0)</code> into the loop. It worked, but it was a blunt instrument. It forced the continuation to the back of the <em>entire</em> macrotask queue, losing its priority context.</p>
<p>In 2026, we use the <strong>Prioritized Task Scheduling API</strong>.</p>
<p><code>await scheduler.yield()</code> is the surgical way to breathe. Unlike <code>setTimeout</code>, it yields to the browser to let it paint, but then it picks your task back up with the <strong>same priority</strong> it had before. You get the fluidity of a paint-cycle without the overhead of re-queuing from scratch.</p>
<h2>Defending the INP (Interaction to Next Paint)</h2>
<p>If you’re building a Product, you aren't just "writing code"—you're managing <strong>Interaction to Next Paint (INP)</strong>.</p>
<p>If your AI loop consumes the thread for more than 50ms, your INP is going to blow past the 200ms "Good" threshold. You might think your app is fast because the tokens are appearing, but your user thinks it's broken because they can't click "Settings" while the AI is talking.</p>
<p><strong>Main Thread Hygiene</strong> is the new performance frontier.</p>
<p>JavaScript</p>
<pre><code class="language-plaintext">// Don't just loop. Orchestrate.
async function processAIStream(stream) {
  let lastYield = performance.now();

  for await (const token of stream) {
    updateUI(token);

    // If we've been working too long (50ms+), 
    // give the browser a chance to paint and handle user clicks.
    if (performance.now() - lastYield &gt; 50) {
      // The 2026 approach: Yield while maintaining priority
      if (typeof scheduler !== 'undefined' &amp;&amp; scheduler.yield) {
        await scheduler.yield();
      } else {
        // Fallback for older environments
        await new Promise(resolve =&gt; setTimeout(resolve, 0));
      }
      lastYield = performance.now();
    }
  }
}
</code></pre>
<h2>The Bottom Line</h2>
<p>In an era where AI can write the logic for you, your value as an engineer isn't in writing the loop—it's in <strong>orchestrating the execution.</strong> If you can't keep your INP under 200ms while running a local LLM or a heavy stream, you aren't building a 2026 product; you’re building a legacy site with a modern coat of paint.</p>
<p>Stop treating the Event Loop like a black box. <strong>Start yielding.</strong></p>
]]></content:encoded></item><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>