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)