Development happens on fast laptops with fast connections, and that hides the single biggest cost on mobile: the CPU time to parse, compile and execute JavaScript. A mid-range Android has roughly a quarter of the single-core performance of a current flagship, and it thermally throttles below that after a few minutes of use. The same bundle that costs 200ms at your desk costs well over a second in your users' hands.
01Bytes are cheap now; CPU is not
Compression and modern networks have made transfer size a smaller share of the problem than it was. What has not improved at the same rate is the cost of turning JavaScript into running code - parse, compile and execute all happen on the main thread, and on a mid-range device that is several hundred milliseconds per hundred kilobytes of script.
That is why two pages with identical transfer sizes can differ by seconds. An image is decoded off the main thread and does not block interaction; the equivalent weight in JavaScript blocks everything, including the tap the user just made. Measure your budget in main-thread milliseconds on a target device, not in kilobytes over the wire.
| Payload type | Transfer | Main-thread cost | Blocks interaction |
|---|---|---|---|
| JavaScript | ~0.6s | 1.5 - 3s parse, compile, execute | Yes, entirely |
| CSS | ~0.6s | ~100ms parse and style | Blocks render, not input |
| Images (WebP/AVIF) | ~0.6s | Decoded off main thread | No |
| Web fonts | ~0.6s | Minimal | Text invisible during swap window |
02Test on the device, not the emulator
Browser throttling gives you a rough network simulation and a crude CPU multiplier. It does not reproduce thermal throttling, memory pressure from other applications, an older browser engine, or the way a cheap flash chip slows cache reads. Teams that only use simulated throttling consistently under-estimate real-world timings.
Buy two or three representative devices from your actual analytics - not the cheapest phone on the market, the phones your users have - and keep them on the desk. Then back that with field data segmented by device class. The aggregate p75 hides that your low-end segment is two to three times worse, and that segment is often where the growth is.
03Third-party scripts are usually the largest single cost
Tag managers, analytics, session recording, chat widgets, A/B testing, consent tools and ad scripts routinely add more main-thread work than the entire application. They are added by people who do not see the performance dashboard, they are rarely removed, and each executes with full privileges on your critical path.
Audit them with the main thread in mind rather than by transfer size. A synchronous A/B testing script that blocks render to prevent flicker can add a second before anything paints. Session recording that observes every DOM mutation degrades interaction responsiveness for the whole session. Load what you keep after interaction where possible, use facades for embeds so the heavy widget only loads on tap, and set a hard rule that a new tag requires removing an old one.
- Measure third parties by main-thread time, not by kilobytes.
- Replace chat and video embeds with a facade until interaction.
- Delay analytics until after the first meaningful interaction.
- Give tag manager containers an owner and a review cycle, or they only grow.
04Ship less JavaScript before optimising the JavaScript you ship
Route-level code splitting is the highest-yield change for most applications - a product page has no reason to carry the checkout flow, the admin views and three date libraries. Split on route, then on interaction, so components behind a click load when it happens.
Then look at what dependencies actually cost. A date library that is a hundred kilobytes for two format calls, a full icon set for eight icons, a heavy state library on a page with barely any state. And send server-rendered HTML for the first view: hydration is real main-thread work, so prefer static output or partial hydration where a route does not need interactivity. The fastest JavaScript is the JavaScript that was never on the critical path.
- Split per route first, then per interaction.
- Audit dependency cost per feature - many can be replaced with a few lines.
- Server-render the first view; hydrate only what is interactive.
- Enforce a main-thread budget in CI, not just a bundle-size budget.
05Images and fonts: mechanical wins that are still missing
Serving desktop-resolution images to a 360-pixel-wide viewport is still common, and it wastes both bandwidth and decode time. Responsive srcset with sensible breakpoints, modern formats, and explicit width and height so layout does not shift. Lazy-load everything below the fold and never lazy-load the hero, which is usually the LCP element.
Fonts cost less than people fear if handled properly: subset to the characters you need, self-host to avoid a second connection setup, preload the one or two weights used above the fold, and use font-display swap with a fallback whose metrics are matched so the swap does not reflow. Four weights and two families is a common default and rarely a deliberate decision.
06Network variability is the thing you cannot engineer away
Real 4G is not a steady pipe. Latency on a congested cell varies from tens of milliseconds to several seconds, connections drop entirely in lifts and on trains, and the radio takes time to wake from idle - so the first request after a pause carries a penalty no amount of bandwidth removes.
Design for it rather than against it. Reduce the number of round trips on the critical path, because latency dominates when bandwidth is adequate. Cache aggressively with a service worker so a repeat visit does not depend on the network at all. Make requests retryable and idempotent, keep an offline state that is honest instead of a spinner that never resolves, and give every interaction immediate local feedback so a slow network feels like a delay rather than a broken interface.
Topics
Aarav Patel
Principal Engineer · SyncTrix
Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.
Building something like this?
SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.