DEV Community

Cover image for Cutting React Bundle Size by 72%: A Deep Dive into main.chunk.js & vendor.chunk.js
saijami
saijami

Posted on

Cutting React Bundle Size by 72%: A Deep Dive into main.chunk.js & vendor.chunk.js

When I ran npm run build on our React production app, the numbers were alarming:

  • main.chunk.js: ~2.27 MB (gzip)
  • vendor.chunk.js: bloated with libraries used only in edge flows
  • Warning: “The bundle size is significantly larger than recommended”

This post breaks down how I reduced the frontend bundle size by 72%, focusing specifically on:

  • Component lazy loading
  • Route-based lazy loading
  • Dynamic imports
  • Minification
  • And how these changes directly impacted main.chunk.js and vendor.chunk.js

Understanding the Problem: Why main.chunk.js & vendor.chunk.js Matter

Before optimization:

main.chunk.js
Contained:

  • App bootstrap logic
  • All routes
  • All components (even unused ones)
  • Business-heavy modules (Inventory, POS, Admin, etc.)

vendor.chunk.js
Contained:

  • React + ReactDOM
  • UI libraries
  • Utility libraries
  • Large dependencies used only on specific pages

This meant every user downloaded everything upfront, regardless of what they actually used.

1️⃣ Component-Level Lazy Loading

❌ Before: Eager Component Imports

import HeavyTable from "./components/HeavyTable";
import Charts from "./components/Charts";

Even if these components rendered conditionally, they were still bundled into main.chunk.js.

✅ After: Component Lazy Loading
const HeavyTable = React.lazy(() =>
import("./components/HeavyTable")
);
const Charts = React.lazy(() =>
import("./components/Charts")
);

Wrapped with:
<Suspense fallback={<Loader />}>
<HeavyTable />
</Suspense>

📉 Impact on Bundles

  • main.chunk.js stopped carrying rarely-used components
  • New component-specific chunks were created
  • Initial JS execution dropped significantly

2️⃣ Route-Based Lazy Loading

❌ Before: All Routes in main.chunk.js
import Inventory from "./pages/Inventory";
import POS from "./pages/POS";
import Admin from "./pages/Admin";

Every route → bundled upfront.

✅ After: Route-Based Lazy Loading
const Inventory = React.lazy(() => import("./pages/Inventory"));
const POS = React.lazy(() => import("./pages/POS"));
const Admin = React.lazy(() => import("./pages/Admin"));

<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/inventory" element={<Inventory />} />
<Route path="/pos" element={<POS />} />
<Route path="/admin" element={<Admin />} />
</Routes>
</Suspense>

📉 Impact on Bundles
main.chunk.js now only contains:

  • App shell
  • Router
  • Core layout
  • Each route became an independent chunk
  • Users download code only when they navigate

3️⃣ Dynamic Imports (Targeting Vendor Bloat)

Some libraries were used only in specific flows (exports, charts, reports).

❌ Before: Static Import
import ExcelJS from "exceljs";

Now every user downloads ExcelJS, even if they never export anything.

✅ After: Dynamic Import
async function exportData() {
const ExcelJS = await import("exceljs");
// use library here
}

📉 Impact on Bundles

  • vendor.chunk.js shrank significantly
  • Heavy libraries moved to on-demand chunks
  • Vendor code stopped blocking initial render

4️⃣ Minification: Squeezing the Final Bytes

Minification is not optional — it’s the last mile optimization.

What Minification Did:

  • Removed dead code
  • Shortened variable names
  • Eliminated whitespace & comments
  • Optimized conditionals

Result:

  • Smaller main.chunk.js
  • Smaller vendor.chunk.js
  • Faster parse & execution time in the browser

📊 Before vs After: Real Build Output

Before Optimization

  • main.chunk.js: ~2.27 MB (gzip)
  • vendor.chunk.js: Large, monolithic
  • Single heavy JS download
  • Long main-thread blocking

After Optimization

  • main.chunk.js: ~615 KB (gzip)
  • vendor.chunk.js: Lean core dependencies only
  • Dozens of feature-based chunks
  • JS downloaded progressively

👉 72% total bundle size reduction

⚡ What Happens to App Performance When These Shrink?

Reducing main.chunk.js and vendor.chunk.js directly impacts:

🚀 Faster Initial Load

  • Less JS to download
  • Faster Time-to-First-Byte (TTFB)
  • Faster First Contentful Paint (FCP)

🧠 Faster JS Parsing & Execution

  • Browsers spend less time parsing JS
  • Main thread is unblocked sooner
  • UI becomes interactive faster

📱 Better Performance on Low-End Devices

  • Lower memory usage
  • Less CPU pressure
  • Fewer dropped frames

🔄 Better Long-Term Scalability

  • New features don’t inflate initial load
  • App remains fast as it grows

Final Takeaway

Most React bundle issues are architectural, not tooling problems.

If your:

  • main.chunk.js is massive
  • vendor.chunk.js keeps growing

Then:

  • Your app is doing too much upfront
  • And users are paying the cost

Fix the loading strategy —
the bundle size will follow.

Top comments (0)