DEV Community

Gerus Lab
Gerus Lab

Posted on

Your Frontend Is Embarrassingly Bloated — And Your Users Are Paying for It

Your Frontend Is Embarrassingly Bloated — And Your Users Are Paying for It

A New York Times article page that weighs 49 megabytes. Four hundred and twenty-two network requests. Two full minutes to load.

We saw this stat circulating last week and our first reaction at Gerus-lab was equal parts disgust and recognition — because we've inherited projects built exactly like this. And we've had to fix them.

This post is about the uncomfortable truth behind modern web performance: most apps are bloated not because of complexity, but because of developer convenience disguised as best practices.


The Numbers Are Worse Than You Think

Let's put 49MB in perspective:

  • Windows 95 fits in 28 MB
  • The original Doom game: ~2.5 MB
  • The entire Apollo 11 guidance system source code: ~4 MB

And yet a single news article — a few paragraphs of text and some images — ships 49 MB of JavaScript, fonts, trackers, and analytics scripts that the user never asked for.

This isn't just a NYT problem. The median mobile page weight in 2025 crossed 3.1 MB. The median desktop page: 2.6 MB. And JavaScript alone accounts for more than 500 KB on average — code that has to be parsed, compiled, and executed before anything appears on screen.

Your users are paying for this with:

  • Their mobile data plan
  • Their battery life
  • Their time
  • Their patience (they already left)

How We Got Here: The "Just Install a Package" Mentality

When we audit inherited codebases at Gerus-lab, we find the same patterns over and over:

The dependency monster. A typical React app ships with moment.js (67 KB gzipped) when date-fns would cover the use case at 12 KB. It ships lodash for one _.groupBy call. It ships a full UI component library when you're using 3 out of 200 components — and tree-shaking isn't set up correctly.

Dead code nobody removed. Feature shipped, feature deprecated, bundle size stays the same. We've seen codebases where 30-40% of the JavaScript shipped to users was code paths that hadn't been touched in 18 months.

Unoptimized images. A hero image that's 3200px wide served to a 390px mobile screen. PNG where WebP would be half the size. No lazy loading. No responsive srcset. Just a <img src="huge.jpg"> and a prayer.

Third-party scripts. Analytics, chat widgets, A/B testing tools, heatmap recorders, retargeting pixels — each one "just 10 KB" but they add up fast. And they all block rendering.


The Performance Tax Is Real

Here's what slow loading actually costs you:

Conversion rates: Google found that a 1-second delay in mobile load time reduces conversions by up to 20%. A page that goes from 5s to 1s load time sees 2-3x higher conversions in e-commerce.

SEO: Google's Core Web Vitals are a ranking factor. A bloated page with poor LCP (Largest Contentful Paint) and high CLS (Cumulative Layout Shift) is actively pushed down in search results.

Retention: 53% of mobile users abandon sites that take longer than 3 seconds to load. Not 10 seconds. Three.

When we worked on a SaaS dashboard product (you can see our case studies at gerus-lab.com), we inherited a codebase with a 4.2 MB initial JavaScript bundle. The dashboard took 6+ seconds to become interactive on an average 4G connection. After our audit and optimization work — 1.1 MB bundle, sub-2s TTI. Retention improved measurably within two weeks.


The Fix Isn't Complicated — It's Just Work

No silver bullet here. But here's what actually moves the needle:

1. Audit Your Bundle First

npx webpack-bundle-analyzer stats.json
# or for Vite:
npx vite-bundle-visualizer
Enter fullscreen mode Exit fullscreen mode

You'll be shocked. We always are when we first run this on a new project. That one chart conversation changes how the whole team thinks about imports.

2. Code Split Everything

Don't load what the user doesn't need yet.

// Before: everything loaded upfront
import { HeavyChartLibrary } from 'heavy-charts';

// After: loaded when user navigates to reports
const HeavyChartLibrary = lazy(() => import('heavy-charts'));
Enter fullscreen mode Exit fullscreen mode

Route-based code splitting in Next.js / React Router is table stakes. If you're not doing this, stop reading and go set it up right now.

3. Replace Fat Dependencies

Instead of Use Savings
moment.js date-fns / dayjs ~55 KB gzipped
lodash (full) lodash-es (tree-shaken) ~40 KB
axios native fetch ~12 KB
full Ant Design cherry-picked components 100+ KB

4. Images: The Low-Hanging Fruit

<!-- Stop doing this -->
<img src="/hero.png" alt="hero">

<!-- Start doing this -->
<picture>
  <source srcset="/hero.webp" type="image/webp">
  <img 
    src="/hero.jpg" 
    alt="hero"
    loading="lazy"
    width="1200" 
    height="600"
  >
</picture>
Enter fullscreen mode Exit fullscreen mode

Use sharp in your build pipeline to auto-generate WebP versions. Use next/image if you're on Next.js — it handles most of this for you.

5. Audit Third-Party Scripts Ruthlessly

Every analytics tool, chat widget, and pixel comes with a negotiation: "our script won't hurt performance." They're lying. Or rather, they're not thinking about the cumulative effect.

Load non-critical third-party scripts with defer or async. Consider loading them only after the main content is interactive:

// Load chat widget only after user interaction
document.addEventListener('mousemove', loadChatWidget, { once: true });
document.addEventListener('touchstart', loadChatWidget, { once: true });
Enter fullscreen mode Exit fullscreen mode

6. Measure Continuously, Not Once

Performance regressions happen gradually. Set up automated Lighthouse CI to fail your PR pipeline if key metrics drop:

# lighthouse-ci.yml
assert:
  assertions:
    first-contentful-paint:
      - warn
      - maxNumericValue: 2000
    interactive:
      - error
      - maxNumericValue: 5000
Enter fullscreen mode Exit fullscreen mode

The Uncomfortable Conversation

Here's what we tell clients who come to us with slow products: this didn't happen by accident.

It happened because the team was optimizing for developer experience, not user experience. Because "we'll fix performance later" is the most expensive technical debt you can accumulate. Because nobody was measuring.

The businesses we work with at gerus-lab.com often come to us after their previous dev team delivered something that technically works but performs terribly at scale. The rebuild or optimization cost is always higher than it would have been if performance was a first-class concern from day one.

Build performance monitoring into your development process from the start. Treat a 500 KB bundle increase like a production bug — because for your users, it is.


The One Metric to Watch

If you do nothing else, track your Total Blocking Time (TBT) on mobile, on a mid-tier device (Moto G4 equivalent), on a simulated 4G connection.

Open Chrome DevTools → Lighthouse → Mobile. If your TBT is over 300ms, you have work to do.

This number tells you more about the real user experience than any other single metric. It's the time your JavaScript is blocking the main thread and making the page feel frozen to the user.


Bottom Line

The web got fat because nobody was watching the scale. Your users don't care about your tech stack, your developer experience, or your clever abstractions. They care about whether the page loads before they get bored and leave.

Performance is a feature. It compounds with SEO, conversions, retention. It's also increasingly a competitive differentiator — most of your competitors are shipping bloated apps too, and a 2x faster experience is something users notice even if they can't articulate why.

Start with the bundle analyzer. Fix the obvious wins. Set up continuous measurement. Repeat.


Need help building a fast, scalable frontend product? We've shipped 14+ products where performance was non-negotiable — from DeFi dashboards that render 10k data points in real time to AI SaaS platforms with sub-second interactions. Let's talk → gerus-lab.com

Top comments (0)