beginner 2 min answer

A build gate caps a route's JavaScript at 280 KB and has passed every build for a year. DevTools reports 1.1 MB of script for the same route, and a mid-range Android phone spends roughly 1.4 s executing before the first tap registers. Both numbers are correct. Why do they differ by about four times, and which one should the budget be written in?

performance-budgetcompressionparse-costmain-threadmobile
Show the full answer Hide the answer

The mechanism

The two numbers measure different things, and only one of them is what the CPU has to chew through.

The 280 KB is transfer size: the bytes that cross the network after the CDN applies Brotli or gzip. Minified JavaScript is highly repetitive text, so it compresses well — a ratio of roughly 3x to 4x is normal. The 1.1 MB is decoded size: what the bytes become once the browser has decompressed them, which is what the parser and the compiler actually read.

Network cost scales with the compressed number. Main-thread cost scales with the decoded number. Those are separate budgets because they bind on separate hardware. A phone on good Wi-Fi downloads 280 KB in well under a second and then spends longer than that parsing and compiling what it unpacked.

Why the decoded number hurts more than it looks

Parse and compile throughput on a mid-range Android device is on the order of 0.5–1 MB of JavaScript per second, against several megabytes per second on a developer laptop. The same bundle is a rounding error on one machine and over a second of frozen main thread on the other, which is why the gate passing tells you nothing about the field.

Engines do reduce this: V8 lazily compiles function bodies, so code that is never called is parsed cheaply rather than fully compiled. That helps, and it does not help the part that runs at start-up — module top level, framework initialisation, polyfills and anything imported eagerly — which is exactly the code that stands between the user and their first tap.

What to write the budget in

Write both numbers and gate on the one that is your constraint.

Your users are limited by Gate on Because
Network (2G-class links, high latency) compressed bytes per route transfer time dominates
CPU (mid-range and older phones) decoded bytes and main-thread time on a named device parse, compile and execution dominate

For most consumer products in 2025 the binding constraint is CPU, so the useful budget is decoded bytes plus a measured ceiling on main-thread time before first input on a named reference device, with compressed bytes kept as a secondary signal for the network.

Common weak answers

  • "The build tool is reporting wrong." Both tools are right. The gap is compression, and treating it as a bug wastes a day.
  • "Just enable Brotli." It is already on, which is why the gate reads 280 KB. Better compression lowers transfer time and does nothing to parse cost.
  • "Set the budget to 1.1 MB then." Changing the number without changing the unit leaves the same blind spot; what matters is which unit the gate is expressed in.
  • "Measure on a laptop and divide by four." Device CPU differences are not a single multiplier, and the spread across a real user population is wide enough that a guess is worse than one cheap physical phone wired into CI.