The device we design against is not the one on our desks. It is a three-year-old mid-range Android on a patchy 4G connection, because that is what a large share of our clients' visitors in India are actually holding.
That choice does more for the finished site than any optimisation pass afterwards, because it changes what gets designed in the first place.
The budget
Agreed before the first commit, written into the statement of work, and checked in CI:
- Largest Contentful Paint under 2.5s on a throttled mid-tier profile, not on desktop fibre.
- Interaction to Next Paint under 200ms.
- Cumulative Layout Shift under 0.1.
- Under 200KB of JavaScript on the critical path, compressed.
- 60fps sustained through the hero and any scroll-driven section.
The last one is the one people underestimate. A site can pass every Core Web Vital and still feel cheap, because the scroll stutters. Frame rate is the part users feel without being able to name.
Measuring the thing users feel
Lab numbers on a fast machine are close to meaningless. We use three checks:
Throttled lab runs in CI. Every pull request, on a fixed CPU and network profile. Budgets fail the build rather than generating a warning nobody reads.
A real device on a real desk. One mid-range Android, physically present. It catches things emulation does not: thermal throttling after thirty seconds of animation, GPU memory limits on large textures, the way a cheap panel renders a subtle gradient as visible bands.
Field data after launch. Lab conditions are a guess about the field. Real-user metrics tell you whether the guess was right, and they are the only numbers that count.
The three things we cut first
When the budget slips — and it does, usually late, usually because of something that "looked fine" — we cut in this order.
One. Scroll-linked effects that are not load-bearing. Parallax on a decorative image, elements that translate as you scroll. These cost a layout or paint on every frame and contribute little. A section that reveals once on entry costs almost nothing; a section that recalculates continuously costs the whole frame budget. We keep the reveal, drop the continuous link.
Two. Backdrop blur on anything that moves or repeats. backdrop-filter is beautiful and expensive. On a fixed header that changes state once, it is affordable. Behind a scrolling list, over a video, or on several elements at once, it will take a mid-range GPU apart. The fallback is a solid colour at 92% opacity, and honestly most people cannot tell.
Three. Large WebGL surfaces at full device pixel ratio. We cap pixel ratio at 1.7 rather than rendering at 3x on a phone that will never resolve the difference, and we stop rendering entirely once the canvas scrolls out of view. A hero animation that keeps running off-screen is burning battery to produce nothing.
What we do not cut: type, spacing, contrast, or the reveal on first view. Those are the design. If the budget cannot survive with them intact, the concept was too expensive and the right fix is upstream, not a compression setting.
The part that actually matters
Most performance problems are decisions, not code. A video background is a decision. Four typeface weights is a decision. A third-party chat widget that loads 300KB before anyone asks a question is a decision — usually taken by someone who never saw the cost.
We price the decisions when they are made, while changing them is free. After launch, "can we make it faster" means reopening choices everyone has grown attached to, and that is a much harder conversation than the one at the start.