There is a particular kind of demo that goes badly. The client is in the room, the laptop is on the office fibre, and the dashboard loads in 400 milliseconds. Everyone nods. Two weeks later a sales rep opens the same dashboard on a phone somewhere between Harare and Bindura, waits, waits, gives up, and phones the office to ask someone to read the numbers out loud.
Nothing was broken. The application worked exactly as built. It was simply built by people who had never experienced it.
The budget is time, not bytes
We stopped talking about page weight internally because it invites the wrong argument. Someone always says "it is only 300 KB more" and they are right, in isolation. So we talk about time on a defined worst case instead.
Our default target for a public marketing or dashboard page: interactive in under five seconds on a throttled 3G connection with 400 ms of round-trip latency, on a mid-range Android device that is four years old. Not the newest iPhone on office WiFi. That device and that connection are the test.
It is a target you can actually fail, which is the whole point. Chrome DevTools can simulate it, Lighthouse can score it, and CI can refuse a merge over it. A target you cannot fail is a slogan.
Latency is the enemy, not bandwidth
This is the part that surprises people. Bandwidth across the region has genuinely improved. Latency has not improved nearly as much, and latency is what kills you, because it is charged per round trip rather than per byte.
Consider a page that makes eleven sequential requests: HTML, then a stylesheet, then a font file discovered inside that stylesheet, then a JS bundle, then three API calls the bundle discovers on mount, then an avatar image per row. It has spent eleven round trips before it renders anything useful. At 400 ms each, you have burned four and a half seconds and you have not yet drawn a pixel of content.
So the highest-leverage work is almost never minification. It is removing round trips:
- Server-render the first screen with its data already in it. Inertia gives us this essentially for free, because the props ship with the document. One round trip, not three.
- Preload the fonts you actually use, self-hosted. A font discovered inside a stylesheet costs a DNS lookup, a TLS handshake, and a fetch, all serialised. Self-host it and preload it and it costs almost nothing.
- Stop fetching on mount. If the component needs data to be useful, the data should have arrived with the page. Fetching on mount is a round trip you chose to pay for.
Ruthlessness about JavaScript
The single largest cost in most applications we inherit is JavaScript that exists to do something CSS already does. Animation libraries for a fade-in. A date picker with locale data for every calendar system in the world, shipped to select a delivery date. A 90 KB icon library imported wholesale for the eleven icons on screen.
Our rules are boring and they work:
- If CSS can do it, CSS does it. A scroll-triggered reveal is an
IntersectionObserverand a class, not a dependency. - Import the icons, never the icon set.
- Anything below the fold is lazy. The chart library loads when the chart scrolls into view, not before.
- Every new dependency has to survive someone asking, out loud, what it costs on the test device.
Assume the connection will drop, because it will
Load-shedding is not an edge case here, it is Tuesday. Neither is a mobile connection that vanishes in a lift. An application that treats a dropped request as an unrecoverable error is an application that will lose someone's work.
What that means in practice: forms keep their state locally until the server confirms the write. Failed mutations retry with backoff rather than throwing away the payload. Anything destructive is idempotent, keyed on a token generated by the client, so a retry cannot double-charge or double-submit. The user gets told what is happening in words, not a spinner that lies.
The reason this is not a compromise
Here is what we did not expect when we started working this way. Every one of these decisions makes the fast experience better as well.
Server-rendering the first screen removes a loading skeleton for everybody. Cutting the JavaScript bundle makes the laptop on fibre feel instant instead of merely quick. Idempotent writes stop the double-submission bug that was always going to happen. Optimistic UI with a real rollback path is simply a nicer interface than a disabled button and a spinner.
Designing for the worst realistic case does not mean building a lesser product for everyone else. It means the constraints do the design work for you, and they have better taste than most product committees.

