← All essays

Building for the Slow Connection

A field guide to the constraints most of us never feel — and a few concrete habits for making the web usable on the networks and devices the majority of people actually have.

I spent a week last winter on a connection I couldn’t control — a rural cabin, one bar of a tired cellular signal, weather doing the rest. It was the most useful week of my professional year, because for seven days I experienced the web the way a large fraction of the world experiences it all the time: slow, flaky, and quietly hostile.

Sites I’d built felt different from the inside of that connection. Things I’d been proud of — a richly animated landing page, a single-page app that fetched everything client-side — became ordeals. And the sites that worked, that loaded and let me do the thing and got out of the way, had a quality in common I want to name and defend.

The thing that worked: HTML that does its job

The sites that survived the bad connection were the ones built on a foundation of HTML that worked before any JavaScript arrived. A form that posted to a server. A link that was a link. Content that was in the document, not assembled from six API calls after the framework finished booting.

This is unfashionable to say, but the slow connection makes it undeniable: every layer you put between the user and the content is a layer that can fail to load, and on a bad network, layers fail constantly. The page that ships its content as content has already won, because there’s nothing left to break. The page that ships an empty shell and a promise to fetch the content is making a bet — and on one bar of signal, that bet loses all the time.

Progressive enhancement is a resilience strategy

We frame progressive enhancement as an accessibility nicety or a backwards-compatibility chore. It’s neither. It’s a resilience strategy, and the slow connection is where its value becomes obvious. Build the thing so it works as plain HTML, then layer enhancement on top, and a failure in any upper layer degrades to something usable rather than a blank screen with a spinner that will spin until the user gives up.

The newsletter form on this very site works this way: with JavaScript, it submits in place and shows you a tidy message; without it, the form posts straight to the hosted signup page. The reader on the bad connection — the one whose script never loaded — still gets to subscribe. That’s not a fallback I bolted on. It’s the base case, and the nice version is the enhancement.

Weight is the enemy you can measure

Resilience is half the battle; the other half is just size. On a fast connection, a heavy page is rude. On a slow one, it’s exclusionary — it can push the time-to-usable past the point where a human will wait, which on mobile is shorter than anyone wants to believe.

So the habits are unglamorous and they all point the same direction: ship less.

A short list that does most of the work

  • Send HTML, not instructions to build HTML. Pre-render. Let the server do the assembly once, at build time, so the user’s tired device doesn’t have to do it on every visit.
  • Make images carry their weight. Modern formats, real dimensions to prevent layout shift, lazy-loading below the fold, and no decorative image that a CSS gradient could replace.
  • Treat every third-party script as a guest who has to justify the visit. Each one is weight you’re spending on the user’s behalf, and most of them are heavier than the value they return.
  • Prefer the platform. A native form control, a CSS transition, an HTML element with built-in behavior — these cost nothing to download because they’re already on the device. Reach for the framework only when the platform genuinely can’t do the job.

None of this is clever. That’s the point. Building for the slow connection isn’t about exotic techniques; it’s about a thousand small refusals to add weight, each one giving a little time back to someone who’s short on it.

Designing for flakiness, not just slowness

The slow connection taught me one more thing the fast one hides: real networks don’t fail cleanly. They don’t go from “online” to “offline.” They go flaky — a request succeeds, the next times out, the one after that returns half a response. An interface that assumes the network either works or doesn’t will shatter on a connection that does neither.

So the resilient page assumes flakiness as the default. It tolerates a failed request without losing the user’s work. It makes actions retryable. It tells the user honestly what state things are in rather than hanging in an ambiguous spinner. This is more work than assuming the happy path — but the happy path is a fiction outside the office, and the people most affected by the fiction are the ones with the least ability to route around it.

Who you’re really building for

It’s tempting to file all of this under “edge cases” and move on. But the slow, flaky connection on the aging device isn’t an edge. It’s the median experience for an enormous number of people, and treating it as exceptional is just a way of saying those people don’t count.

They do. And building for them turns out to make the web better for everyone — faster, more resilient, less fragile in the face of the ordinary chaos of real networks. The cabin with one bar of signal isn’t a worst case to be tolerated. It’s the honest test, and a web that passes it is a web that works.

Last updated