Perspective · 4 min read

The phone someone actually has

Mobile-first is usually a layout instruction. Treated as an engineering constraint instead, it changes what you are allowed to ship.

Mobile-first, in most projects, means the designer drew the narrow layout first. The engineering is unchanged. The same JavaScript ships, the same fonts load, the same third-party tags fire. The layout reflows and everyone calls it responsive.

That is a layout instruction. It is not a constraint, because nothing can fail it.

Treated as an engineering constraint — a budget with a number, enforced by something that can stop a merge — it starts telling you what you may not ship.

Test on the conditions, not the office

The gap between a development machine and a real one is wide in a way that is easy to forget while you are inside it. Fibre, a laptop with a fast processor, a warm cache, and a browser you left open.

A meaningful share of South African web traffic is a mid-range Android phone on mobile data, often prepaid, on a connection whose quality varies with where the person is standing. A site that is fast on office fibre and slow on that phone is not a fast site. It is a site that is fast for the people who built it.

So the budgets are asserted against throttled conditions, in continuous integration, and a regression fails the build. Not a warning. A failure, on the pull request that caused it, in front of the person with the context to fix it.

What the numbers ended up being

  • **Client JavaScript under 130KB gzipped.** We are at 35.6KB. The headroom is deliberate — it is what pays for interactivity later without a renegotiation.
  • **Largest Contentful Paint under 2.5 seconds**, Cumulative Layout Shift under 0.1.
  • **The HTML document under 120KB**, fewer than 25 requests on first load.
  • **No third-party origins on the homepage.** Asserted, not aspired to.

That last one is the constraint with the most teeth, and it is worth being specific about why.

Every third-party origin is a promise you did not make

A font from a font CDN, an analytics tag, an embedded video player, a chat widget. Each one is a DNS lookup, a TLS handshake, and a request to a server you do not control, on a connection you cannot predict, before your page is usable.

On fibre they cost milliseconds and nobody notices. On a congested mobile connection each new origin is a fresh negotiation, and the failure mode is not slowness — it is a page that hangs waiting for something that is not coming.

So fonts are self-hosted and subset. Analytics is designed to be non-blocking or absent. There is no chat widget. The homepage makes requests to exactly one origin: ours.

This is also the least popular constraint, because every one of those tools is individually justifiable. The discipline is that they are justifiable individually and expensive collectively, and the only place that shows up is a number that fails a build.

The form works without JavaScript

The clearest expression of the constraint is the conversation form.

Without scripting, the path is chosen with a link, every fieldset renders in one long form, and it submits and validates on the server normally. With scripting, the same markup becomes a multi-step flow with progress and draft persistence.

This is not nostalgia. It is that a multi-step form which needs JavaScript to submit is a form that sometimes does not submit — a failed chunk, a spotty connection, a browser doing something unexpected — and the failure lands precisely on the person who had decided to make contact. Everything else on a marketing site can degrade. The one interaction that carries the business should not.

We verified it by posting to the built application with no client-side JavaScript involved: missing field returns 400 with the error rendered, `aria-invalid` set, the message programmatically associated, and previously entered values preserved. A valid submission returns the confirmation. That is the whole flow, working, with scripting off.

What this rules out

Real things, and it is dishonest to pretend otherwise. Heavy client-side animation libraries. Embedded third-party media on first load. An off-the-shelf chat widget. A component library that ships a hundred kilobytes to render a button.

Each of those has a legitimate use. None of them survives a 130KB budget and a no-third-party-origins rule, which means the constraint is doing its job: it is making the decision at the point where the trade-off is visible, rather than six months later when the site is slow and no single change is responsible.