EverProduct
Frontend

Stage 05 · The Craft

The Budget: Why It Feels Slow

It's instant on your machine. Your machine is a fast laptop on office wifi with a warm cache and twelve test records — and none of those things is true for the person you built it for.

The page opens instantly. You've reloaded it four hundred times today.

Now the same page on a three-year-old mid-range Android, on mobile data, with a cold cache and eight hundred real records: nine seconds before anything appears, then a further two before a tap does anything. The user has already left, and they won't file a bug report — they'll just remember your product as the slow one.

Your laptop is not the world. Every performance decision you make on it is made on behalf of someone with a worse phone and a worse connection.

Two budgets, and everything fits in one

Performance work looks like a hundred unrelated tricks until you notice there are only two resources being spent.

Bytes — everything that has to travel over the network before something can happen. Scripts, styles, fonts, images, data.

Main-thread time — everything the phone's processor has to do with those bytes: parsing, executing, layout, paint. And this is where the gap between your laptop and a cheap phone is widest: the download might be four times slower, but the JavaScript execution can be six times slower, because a megabyte of script is work, not just weight.

Every optimisation below is either "send fewer bytes" or "do less work on the main thread." Knowing which one you're spending is most of knowing what to do.

What to measure

Three numbers describe what a user actually experiences, and they're worth knowing by their plain meanings rather than their initials:

How long until the main content appears. Not when the network finished — when the biggest visible thing is on screen. This is what people mean by "it's slow to load."

How fast it responds when touched. From a tap to a visible reaction. This is the metric that catches the page that looks loaded but ignores you for a second, which is more infuriating than a slow load.

Whether it jumps. Content shifting while someone reads or reaches for a button. Almost always images without dimensions, injected banners, or a font swap changing text size.

Two more things about measuring. Lab measurements — Lighthouse on your machine — are for comparing before and after; they're not the truth about your users. Field data, collected from real sessions, is the truth, and it's usually worse. And the summary score is not the goal: a score is a means of finding the one thing that's actually slow.

Measure, then change

The most common failure in this area isn't ignorance, it's misdirected effort: someone spends a day memoising components while a 900-kilobyte date library and three uncompressed hero images sit untouched.

The discipline is the debugging method from earlier, applied to speed. Record a profile. Find the largest block. Form one hypothesis about it. Change one thing. Measure again. If the number didn't move, undo it — an optimisation that doesn't measurably help is complexity you're paying for and receiving nothing back.

The How to Learn sphere describes the trap precisely: polishing the key that already opens. Optimising the thing you already know how to optimise is comfortable and produces the feeling of work; the actual cost is nearly always somewhere you haven't looked yet.

Where the time usually is

In rough order of how often each is the real culprit:

JavaScript volume. The single biggest cost in modern frontend. Every kilobyte is downloaded, parsed, compiled and executed — on their phone. Split by route so a page ships only what it needs, load heavy components on demand, and look at what's actually in the bundle. Almost every project contains one enormous library imported for one function, and finding it takes ten minutes.

Images. Usually the largest number of bytes. Modern formats are dramatically smaller than the old ones, an image should never be sent larger than it will be displayed, srcset lets the browser choose, loading="lazy" defers what's below the fold, and declared dimensions prevent the jumping described above.

Fonts. Two loaded fonts is a design decision; four is a performance one. Fonts block text from appearing, so use font-display: swap to show something immediately, preload the one that matters, and subset it to the characters you use. And remember system fonts exist and cost zero bytes.

Render-blocking resources. Stylesheets in the head hold up the first paint, and classic scripts hold up parsing. Defer what isn't needed immediately; that's the first article's pipeline being used deliberately.

Third-party scripts. Analytics, chat widgets, tag managers, ads, embeds. This is the least examined and often the largest single cost — a chat widget can weigh more than your entire application, and it runs on the same thread as your interface. Every one of them should have to justify itself, load late, and be measured. A tag manager is especially worth auditing: it's a door through which anyone in marketing can ship code to your users.

Long tasks. Anything holding the main thread for more than a few hundred milliseconds makes the page unresponsive. Break big work into chunks, virtualise long lists so you render forty rows instead of four thousand, avoid interleaving DOM reads and writes, and animate only transform and opacity.

Caching. Fingerprinted filenames plus long cache lifetimes mean a returning visitor downloads almost nothing. A CDN puts those files near the user. Both are configuration rather than code, and both are enormous.

Perceived speed is real speed

The user doesn't have a stopwatch. What they have is a sense of whether the thing is responding, and you can win a lot there without making anything faster.

Respond to every interaction immediately — under about a tenth of a second, a reaction feels instant and caused by them. Even if the result takes two seconds, the button must acknowledge the press now.

Show the shape before the data. A skeleton in the layout of the content that's coming reads as faster than a spinner, and much faster than a blank area.

Be optimistic where you can. Show the message as sent, the item as added, the like as registered — then reconcile with the server. The rule is to only do it where failure is rare and recoverable, and to handle the failure honestly when it happens.

Prioritise what's visible. Load the top of the page first and defer everything below the fold. Nobody notices work done for a part of the screen they haven't reached.

Test like a user

Three habits, all cheap:

Throttle. Devtools can simulate a slow network and a four-times-slower processor. Develop like that one day a week and you'll notice things that are invisible otherwise.

Use a real cheap phone. Emulation doesn't reproduce a real device's thermal behaviour, memory pressure or browser. One mid-range Android on a shelf is the best performance tool a team can own.

Test with real data volumes. Twelve rows behaves nothing like eight hundred. Most rendering disasters are invisible until the data is realistic, which is why they reach production intact.

In practice

Run one measurement before optimising anything. Every time. Without a baseline you can't tell improvement from motion.

Open the bundle breakdown once this week. The first look usually finds something absurd.

Audit third parties: list every external script, name what it's for, and delete or defer one.

Add dimensions to every image and switch below-the-fold ones to lazy loading. Two small changes that fix jumping and a chunk of the byte budget.

Virtualise the longest list in your project, or paginate it. Rendering thousands of rows is a decision, not a requirement.

Keep a before-and-after number for anything you change. It's the difference between performance work and performance theatre — and it's the same rule as never fixing a bug you can't explain.

Check yourself

Close the article and answer in your own words:

  1. What are the two budgets, and why is the CPU gap wider than the network gap on cheap phones?
  2. Name the three things worth measuring, in plain language.
  3. Why is a lab score not the truth about your users?
  4. What's the discipline that stops you optimising the wrong thing?
  5. Why is JavaScript volume more expensive than the same weight of images?
  6. Name three ways to make something feel faster without making it faster.
  7. Why does testing with twelve records hide most rendering problems?

In short

  • Everything spends one of two budgets: bytes over the network, or time on the main thread — and cheap phones are hit hardest by the second.
  • Measure what users feel: when the main content appears, how fast it reacts to a tap, and whether it jumps.
  • Lab numbers compare before and after; field data is the truth, and the score is a means of finding the real problem.
  • Measure, change one thing, measure again — and undo optimisations that didn't move the number.
  • The usual culprits in order: JavaScript volume, images, fonts, render-blocking resources, third-party scripts, long tasks, missing caching.
  • Third-party scripts are the least examined and often the largest single cost.
  • Perceived speed counts: acknowledge every interaction instantly, show skeletons, be optimistic where failure is rare, prioritise what's visible.
  • Develop throttled, test on a real cheap phone with realistic data volumes, and keep a before-and-after number for every change.