DEV Community

Cover image for We Ship 5MB of JavaScript Then Gaslight Users About Their WiFi
Adam - The Developer
Adam - The Developer

Posted on

We Ship 5MB of JavaScript Then Gaslight Users About Their WiFi

Your site takes 10 seconds to load on a phone. Your users complain. You blame their internet connection.

Meanwhile, you just shipped the equivalent of three novels worth of JavaScript so someone can submit a contact form.

This is an intervention. We need to talk about our JavaScript problem, the users we're hurting, and why every excuse we make is bulls.

Table of Contents

  1. Introduction: The Audacity
  2. The Numbers Don't Lie (But We Do)
  3. "But It's All Minified!"—The Compression Cope
  4. The Real Cost Nobody Talks About
  5. Who Actually Pays the Price
  6. Classic Excuses and Why They're Bulls
  7. The Framework Blame Game
  8. What We Could Do (But Won't)
  9. Conclusion: Ship Less, Care More

Introduction: The Audacity

Let's talk about the elephant in the room, or more accurately, the 5MB JavaScript bundle we're forcing users to download before they can click a button.

We've gotten comfortable shipping absolutely obscene amounts of JavaScript to production. Then, when users complain about slow load times, laggy interactions, or their browsers turning into space heaters, we shrug and say "well, maybe they should upgrade their internet" or "works fine on my M4."

The audacity is truly breathtaking.

The Numbers Don't Lie (But We Do)

The median website now ships 500KB of JavaScript just to load. That's the median.

Half the web ships more.

Many now push 2–5MB or more just to show a landing page.

For scale: the entire text of Moby Dick (400+ pages) is only 1.2MB.

We're shipping novels worth of JavaScript to render a signup form and then wondering why pages feel slow.

We're shipping multiple novels worth of JavaScript so users can... read a blog post. Submit a form. View a product page.

But here's where it gets really fun: that's just the transfer size. Once the browser decompresses and parses that JavaScript, the actual memory footprint balloons. Your 2MB bundle becomes 10MB+ in memory. On a phone with 4GB of RAM where half is already used by the OS and other apps.

And we genuinely act surprised when things get slow.

"But It's All Minified!"—The Compression Cope

"It's fine, it compresses well with gzip!"

Cool. You know what compresses even better? Not shipping it in the first place.

Yes, minification and compression help. A 5MB bundle might transfer as 1.5MB over the wire. Congratulations, you've optimized your disaster. The browser still has to decompress it, parse it, compile it, and execute it. None of that work disappears because you ran it through Terser.

Minification is a band-aid on a bullet wound. We're using it as an excuse to avoid the actual problem, which is that we're shipping way too much code.

The Real Cost Nobody Talks About

Let's break down what actually happens when a user loads your JavaScript-heavy site:

Parse time: The browser has to read and parse all that JavaScript. On a modern desktop, maybe that takes 200-500ms. On a mid-range phone from 2020? Try 2-3 seconds. On a budget Android from a developing market? 5-10 seconds.

Compile time: Then it has to compile it to bytecode. Add another chunk of time.

Execution time: Then it actually has to run your initialization code, hydrate your framework, set up your state management, initialize your analytics, load your A/B testing framework, etc.

Memory pressure: All of this sits in memory. On memory-constrained devices, this causes other tabs to get killed, the OS to swap, everything to slow down.

Battery drain: JavaScript execution is CPU-intensive. Every unnecessary framework abstraction is literally draining your user's battery.

But sure, the real problem is their "slow internet."

Who Actually Pays the Price

Here's the thing that really pisses me off: the people who suffer most from our bloated JavaScript bundles are the people who can least afford it.

If you're reading this, you probably develop on a recent MacBook or a high-end Windows machine. Fast CPU, plenty of RAM, gigabit internet or good 4G/5G. Your test devices are probably recent iPhones or Pixel phones.

Your users? They're on:

  • 3-4 year old budget Android phones with 2-4GB of RAM
  • Metered data connections where every MB costs money
  • Spotty rural internet or congested public WiFi
  • Devices that thermal throttle after 30 seconds of JavaScript parsing

We're building for ourselves and calling it "modern web development." Then we're blaming users for not having good enough hardware or internet to handle our carelessness.

It's not just classist, it's lazy.

Classic Excuses and Why They're Bulls

"Users expect a rich, interactive experience"

No, they expect the page to load and work. A form doesn't need React. A blog doesn't need Vue. A product listing doesn't need Angular plus RxJS plus a state management library plus a component library.

You know what users actually expect? To accomplish their task and leave. They don't give a shit about your smooth animations or your fancy state transitions. They want to buy a product, read an article, or fill out a form. Your 3MB of JavaScript is standing between them and their goal while you pat yourself on the back for "craft."

"We need it for the developer experience"

Your developer experience is not more important than your user experience. Full stop. If your DX requires shipping 3MB of runtime to make a button work, your DX is broken.

"But we ship features faster!" Faster for who? You're shipping a slower product to users so you can feel productive in your sprint reviews. That's not a trade-off, that's just selfish.

"It's only loaded once, then cached"

First load matters. A lot. And cache invalidation means users are downloading your new 5MB bundle every time you deploy. Which, if you're doing CI/CD properly, is multiple times a day.

Also, mobile browsers aggressively clear caches to save space. That "cached" bundle? Gone after a week of not visiting. Your returning users are new users, performance-wise. Every. Single. Time.

"We code-split and lazy load"

Great! You've taken your 5MB problem and turned it into twenty 250KB problems that load unpredictably and cause layout shift. You still shipped 5MB of JavaScript, you just made the user download it in annoying chunks while their page jumps around.

Code splitting is good. But it's not a substitute for shipping less code. It's like saying "I didn't punch you once really hard, I punched you twenty times gently." You still got punched.

"The framework handles it efficiently"

The framework IS the problem. React alone is 40KB+ minified and gzipped. Then you add React DOM. Then your routing library. Then your state management. Then your component library. Then your icon library (because importing all of Font Awesome is easier than picking 10 icons).

Before you've written a single line of business logic, you're at 300KB+.

And that's before you imported Lodash because you forgot JavaScript has .map() now, Moment.js for date formatting (193KB for something the browser does natively), and that fancy animation library you used for one fade-in effect.

The Framework Blame Game

Look, I'm not anti-framework. Frameworks solve real problems. But we've normalized using industrial-strength frameworks for problems that don't need them.

You don't need React for a static blog. You don't need Vue for a landing page. You don't need Svelte for a corporate website that updates twice a year.

"But what if we need interactivity later?"

Then add it later. YAGNI applies to frameworks too. The performance cost of shipping a framework "just in case" is real and immediate. The benefit of maybe needing it someday is hypothetical.

We've also gotten really bad at evaluating framework costs honestly:

  • "React is only 40KB!" (Ignoring React DOM, the router, state management, and everything else in your actual bundle)
  • "Next.js is great for performance!" (While shipping 300KB+ before your first component)
  • "This component library will speed up development!" (200KB for a button and an input field)

Every dependency is a promise that it's worth the bytes. Most of them are lying.

What We Could Do (But Won't)

Here's the truly frustrating part: we know how to fix this. The solutions aren't even hard:

Ship less JavaScript

That animated hamburger menu? 50 lines of CSS can do what your 40KB animation library does. Your infinite scroll? Pagination works, costs 2KB, and is actually more accessible. Your fancy form validation? The browser has built-in validation that costs zero bytes and works without JavaScript.

That smooth-scroll-to-top button? CSS scroll-behavior: smooth and an anchor tag. Your modal dialog? The <dialog> element exists. Your custom dropdown? <select> works and is keyboard accessible by default.

Stop reaching for npm to solve problems the platform already solved.

Audit your dependencies ruthlessly

Run npm ls and actually read it. You're shipping three different date libraries. You have two versions of React in your bundle because of a transitive dependency. You imported all of Lodash for _.debounce.

That utility library you added 18 months ago? You're using one function from it. Copy the function and delete the dependency. That component library? You're using 3 components and shipping 47. Extract what you need.

Install bundlephobia and actually look at what you're adding. If you can't justify the bytes, don't add it. "It might be useful" is not justification—it's hoarding.

Measure real-world performance on real-world devices

Stop testing on your developer machine and pretending that's representative. Buy a $150 Android phone from 2021. Use it as your primary test device for a week. Watch your site struggle. Feel the pain your users feel.

Throttle your connection to "Slow 3G" in dev tools and leave it there. If your site doesn't work well on Slow 3G, it doesn't work well. Period.

Check your Core Web Vitals for actual users in the field. If your Largest Contentful Paint is over 2.5 seconds, your users are suffering and you're pretending they're not.

Set and enforce performance budgets

"No page can ship more than 200KB of JavaScript total." Put it in your CI/CD. Fail the build if someone exceeds it. Make them justify why they need more and what they're going to remove to make room.

"Time to Interactive must be under 3 seconds on a mid-range device on 3G." Measure it. Track it. Treat it as a P0 bug when you regress.

Make performance someone's job, not everyone's hope. Without enforcement, these budgets are just wishes.

Consider alternatives that actually respect users

Server-side rendering—the real kind where the server sends HTML, not the kind where you send 500KB of JavaScript to "hydrate" it. If your SSR'd page needs JavaScript to become interactive, you're doing SSR wrong.

Static site generation for anything that doesn't change per-user. Your blog does not need client-side rendering. Your documentation doesn't need React. Your marketing site doesn't need a SPA.

Islands architecture: interactive components in a sea of static HTML. Ship JavaScript only for the parts that actually need it. Let the rest be plain HTML that works instantly.

Web components for reusable pieces without framework overhead. Or—wild idea—just use CSS classes and a tiny bit of vanilla JS.


But we won't do most of this because:

  • It's not as fun as using the new shiny framework we can brag about on Twitter and put on our resume
  • It doesn't look as good on our tech blog where we need to signal we're using "modern" tools
  • It requires actual thought about tradeoffs instead of just npm install-ing our problems away
  • It's easier to blame users' devices than admit we're shipping a worse product to hit an arbitrary sprint deadline
  • Our competitors are doing it so it must be fine (they're also hemorrhaging users on slow connections)
  • Management doesn't understand performance and we're not going to educate them because it's not our job (it literally is)
  • We already architected everything around React and a rewrite would take effort (so would doing your job well)

We choose developer comfort over user experience every single day and then wonder why the web feels slower than it did a decade ago.

Conclusion: Ship Less, Care More

Here's my challenge: next time you're about to add a dependency or adopt a framework, ask yourself:

  • What problem does this solve?
  • What does it cost in kilobytes?
  • Who pays that cost?
  • Is there a lighter-weight solution?
  • Could I write this myself in 50 lines instead of importing 50KB?

If you can't answer those questions, you shouldn't be adding it.

We got into this mess by treating JavaScript like it's free. By assuming everyone has fast devices and fast internet. By optimizing for our convenience instead of our users' experience.

The web is slow because we made it slow. Users' internet connections are fine. Our priorities are what's broken.

Every byte you ship is a choice. Every dependency is a trade-off. Every framework is a bet that your convenience matters more than your users' time, money, and battery life.

Most of us are losing that bet and gaslighting our users about it.

Ship less. Care more. Stop gaslighting users about their WiFi when the real problem is the 5MB of framework code we forced them to download to view a recipe.

Do better. Or at least stop pretending you don't know why your site is slow.

Top comments (0)