
Reduce Bundle Size by 50%, Increase Revenue by 10%: The Art of Code Splitting
Every new feature bloats your JS bundle. Learn practical Code Splitting, Tree Shaking, and Dynamic Import techniques to keep your app fast and lightweight.

Every new feature bloats your JS bundle. Learn practical Code Splitting, Tree Shaking, and Dynamic Import techniques to keep your app fast and lightweight.
Establishing TCP connection is expensive. Reuse it for multiple requests.

Optimizing by gut feeling made my app slower. Learn to use Performance profiler to find real bottlenecks and fix what matters.

Definitely No, Maybe Yes. How to check membership in massive datasets with minimal memory using Bit Arrays and Hash Functions. False Positives explained.

Text to Binary (HTTP/2), TCP to UDP (HTTP/3). From single-file queueing to parallel processing. Google's QUIC protocol story.

In early-stage startups, 'Features' are priority #1.
"Add Charts!", "Add Rich Text Editor!", "Add 3D Viewer!"
Developers happily type npm install heavy-library.
One day, users complain. "It takes 5 seconds to load. I'm leaving."
I checked Lighthouse performance. 30/100. The culprit was a Massive JS Bundle (main.js, 5MB). Even if a user just wanted to see the 'Login Page', they had to download and parse the entire 5MB chunk of JavaScript containing 3D viewers and Editors that they don't even use.
I thought, "Internet is fast these days. 5MB is nothing."
But the bottleneck wasn't just Network (Download Time).
It was the Browser's CPU (Execution Time).
On low-end mobile devices, parsing 5MB of JS takes seconds, freezing the screen (TBT: Total Blocking Time).
The phone gets hot, the battery drains, and the user gets annoyed.
Modern SPA (React/Next.js) bundles everything into one file by default. Unused Admin code slows down the Landing Page. It's extremely inefficient.
I viewed bundling as "Packing for a Trip".
We need to Split the code into small chunks and Load them lazily only when needed.
webpack-bundle-analyzer)Don't guess. X-ray your app.
For Next.js, use @next/bundle-analyzer.
ANALYZE=true npm run build
A colorful Treemap appears. Big squares = Big files.
Common Culprits: moment.js (locales), lodash (unused functions), xlsx (parsers), framer-motion (animations).
Use React.lazy (or next/dynamic) to split code by Page URL.
If a user never visits /dashboard, they never download dashboard.js.
import { lazy, Suspense } from 'react';
// Lazy load the page component
const Dashboard = lazy(() => import('./pages/Dashboard'));
<Routes>
<Route path="/dashboard" element={
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
} />
</Routes>
This is the single most effective optimization for SPAs.
Don't load heavy components (Charts, Map, Editors) initially. Load them On Interaction.
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <p>Loading Chart...</p>,
ssr: false, // Disable Server-Side Rendering for client-only libs
});
return (
<div>
<button onClick={() => setShow(true)}>Show Chart</button>
{show && <HeavyChart />}
</div>
);
This keeps the main.js bundle light and fast (TTI: Time to Interactive).
You can lazy-load plain JavaScript libraries too, not just React Components. Perfect for utility libraries used in specific event handlers.
// Only load 'confetti' when the user wins
const handleWin = async () => {
const confetti = (await import('canvas-confetti')).default;
confetti();
};
return <button onClick={handleWin}>Win!</button>;
This saves ~20KB from the initial load.
"I only used one function, why is the whole library included?" Some libraries (CommonJS) don't support Tree Shaking.
Lodash Example:
// ❌ Imports EVERYTHING (70KB)
import _ from 'lodash';
// ✅ Imports specific file (< 1KB)
import isEmpty from 'lodash/isEmpty';
// 🌟 Best: Use ES Module version ('lodash-es')
import { isEmpty } from 'lodash-es';
Always prefer libraries that ship ES Modules (ESM) because bundlers can easily remove unused code from them.
Here's a representative case of how damaging bundle bloat can be. A SaaS Dashboard launched with a Bounce Rate over 70% — users were leaving before the app even loaded. Average Load Time was 3.5s.
The analysis showed that Chart Libraries (react-vis) and Map Libraries (mapbox-gl) were being downloaded even on the Login Page.
These two libraries alone were 2MB gzipped.
LandingPage bundle from DashboardApp bundle.Result: Load time dropped to 0.8s. Reducing bundle size can dramatically improve bounce rate — there are cases where it dropped from 70% to around 35%. Lesson: Code quality doesn't matter if the user leaves while waiting for it to load.
"Lazy loading makes the user wait when they click. I don't like that delay." Use Prefetching. When the user hovers over the 'Dashboard' link, start downloading the chunk in the background.
Next.js <Link> component does this automatically for viewports.
In React, you can do:
const Dashboard = lazy(() => import('./Dashboard'));
function onHover() {
// Manually trigger download
import('./Dashboard');
}
<Link onMouseEnter={onHover} to="/dashboard">Dashboard</Link>
This gives the best of both worlds: Small Initial Load AND Instant Navigation.
Q: Are there any downsides to Code Splitting? A: Too much splitting creates hundreds of small files, leading to many HTTP requests (though HTTP/2 mitigates this). Split at logical boundaries (Pages, Large Widgets).
Q: How do I handle loading states?
A: Use <Suspense> boundaries. Design a nice "Skeleton UI" so the user perceives the app as loading instantly.
Q: What about CSS? A: Modern tools (CSS Modules, Styled Components, Tailwind) automatically split CSS along with your JS components. You don't need to worry about it manually.
bundle-analyzer to find the fat.lodash-es, date-fns).