Web Performance That Survives Real Networks and Real Devices
A site that feels instant on a developer's machine can be unusable on a mid-range phone over mobile data. Performance is a budget you set and defend, not an optimisation pass at the end.
The measurement gap
Development happens on fast machines, on fast connections, with a warm cache. Real users are frequently on mid-range Android devices over congested mobile networks, arriving cold.
The gap between those two experiences is enormous, and it is invisible unless deliberately measured. This is why performance work that begins with "it feels fine to me" almost always begins wrong.
Measure what users experience
Synthetic lab tests are useful for catching regressions but do not describe your actual audience. Field data — real user monitoring — is what tells you the truth, and the metrics worth tracking are those tied to perception:
- Largest Contentful Paint — when the main content becomes visible
- Interaction to Next Paint — how quickly the interface responds to input
- Cumulative Layout Shift — how much content moves while loading
Track these at the 75th percentile or worse, not the median. The median describes a user having a good day; the tail is where people give up and leave.
JavaScript is usually the problem
On mid-range mobile hardware, the cost of JavaScript is dominated not by download time but by parse, compile and execution — all on the main thread, all blocking interaction.
The practical hierarchy:
- Ship less. Audit dependencies. A date library, a full icon set, or a charting package imported for one chart are common and expensive.
- Split by route. A visitor reading an article should not download the code for the admin interface.
- Defer what is not needed for first interaction. Analytics, chat widgets and carousels can load after the page is usable.
- Prefer server rendering for content that does not need interactivity. HTML that arrives ready to display is faster than JavaScript that arrives and then builds it.
Images are the largest payload
Images typically dominate transferred bytes, and the fixes are well understood and rarely fully applied:
- Serve modern formats — AVIF and WebP are substantially smaller than JPEG at equivalent quality
- Size to the display, not to the original. Serving a 4000 px image into a 400 px slot wastes 99% of the bytes.
- Use responsive sources so a phone receives a phone-sized image
- Set explicit dimensions to reserve layout space and prevent shift
- Lazy-load below the fold, but never the LCP image — lazy-loading the hero delays the metric it defines
Fonts block text
A web font that has not loaded leaves text invisible or swaps it abruptly. Use font-display: swap so text renders immediately in a fallback, preload the critical face, and subset to the characters actually needed.
For a bilingual site this matters twice over, since Arabic and Latin faces are separate files. Subsetting Arabic by range is particularly worthwhile given the size of complete Arabic fonts.
Set a budget and enforce it in CI
Optimisation done once decays. A performance budget — a hard limit on bundle size, image weight, or a metric threshold — checked automatically on every pull request is what keeps the gains.
Without automated enforcement, performance regresses feature by feature, each one individually defensible, and the cumulative result is the slow site you started by fixing.
Test on the hardware your users have
Buy a mid-range Android phone and keep it on the desk. Throttle to a slow connection in developer tools. Load the site cold, with an empty cache, as a first-time visitor would.
Ten minutes doing that reveals more than an afternoon reading synthetic scores.
References
web.dev, Core Web Vitals documentation; Chrome User Experience Report; Osmani, A., Image Optimization.
