Build in public

The Origin You Forgot: I Self-Hosted the JavaScript and Left the Fonts

By Arun Jalota · · ~5 min read

Last week I wrote about finding out my own site rendered a blank page if one CDN was unreachable. I fixed it properly: compiled the JSX ahead of time, moved React, three.js and the smooth-scroll library onto my own origin, and put the static content in the HTML with a watchdog. The payload went from about four megabytes to under a hundred kilobytes. I wrote a test that blocks every third-party request and asserts the page still contains readable text. It passed. I moved on.

The test passed because it was asking the wrong question.

"Does the page still render when third parties are blocked" is a question about availability. The origin I'd missed didn't fail that test, because when you block a request it fails instantly and the browser gets on with its life. Real CDNs rarely fail instantly. They hang.

One line of HTML

The line was this, near the top of every page on the site:

<link href="https://fonts.googleapis.com/css2?…" rel="stylesheet">

A stylesheet. Not a script — I'd audited the script tags, twice, and been satisfied. But a synchronous stylesheet blocks first paint by design. That's not a bug in the browser; it's the browser doing exactly what it's told, because rendering text and then restyling it is a worse experience than waiting a moment. The browser will not paint until it has resolved every render-blocking stylesheet, and it doesn't much care that one of them lives on someone else's infrastructure.

So I measured it, rather than reasoning about it. I stalled fonts.googleapis.com for four seconds and timed how long until the page contained more than two hundred characters of readable text:

ScenarioFirst readable text
Font CDN healthy198 ms
Font CDN stalled 4s4,124 ms
Font CDN stalled 4s, after the fix124 ms

Twenty-one times slower, from one line of HTML, on a site whose entire remaining payload was ninety-seven kilobytes and served from one origin. Every other optimisation I'd made was worth roughly nothing on that connection.

I'd been counting origins in script tags. The browser counts them in the critical path.

The fix is boring, which is the point

You load the stylesheet with media="print", which tells the browser it isn't needed for screen rendering, so it fetches at low priority and doesn't block. An onload handler flips it back to media="all" the moment it arrives. A <noscript> copy keeps it working without JavaScript.

What makes it safe is the thing underneath: the font stack already ended in system-ui, sans-serif. Text paints immediately in the system font and swaps to the webfont when — or if — it shows up. The page is readable in a hundred milliseconds and pretty a moment later, instead of neither for four seconds. If the CDN never answers, a visitor gets my site in their operating system's font, which is a completely acceptable outcome that I had previously chosen not to offer them.

That last sentence is the whole thing. The fallback existed. The typography was decorative. I just hadn't told the browser which was which, so it treated my aesthetic preference as a correctness requirement and made everyone wait.

Why I missed it twice

Three reasons, and I think all three generalise.

I audited by category, not by behaviour. I went looking for third-party scripts, because scripts were what had burned me. A stylesheet didn't pattern-match to "dependency." It should have: anything the browser waits on before painting is a dependency, whatever its file extension.

My test modelled the failure I'd already had. Blocking a request is a clean, fast, honest failure. It's also the rare one. The common failure is slow — a saturated mobile link, a captive portal that swallows requests, a corporate proxy inspecting traffic. A suite that only tests instant failure will happily certify a page that hangs. Adding latency to a test is one extra line and it's the line that found this.

It never happened to me. Same reason as last time. My connection is good, that CDN is one of the best-run on the internet, and the page always painted instantly on my laptop. The failure mode was real the entire time and invisible the entire time, which is exactly what I keep telling other people to go looking for.

The test I have now

Two lines, alongside the block-everything test I already had:

Stall each third-party origin for four seconds. Assert first readable text stays under a second. That's it. It runs on every build, it takes a few seconds, and it would have caught this the first night.

I've argued before that the eval is the real spec for an AI feature — that what you assert is what you actually promised, and everything else is decoration. This is the same claim about performance work. I thought I'd promised "the site works without third parties." What I'd actually asserted was "the site works when third parties fail instantly," and the gap between those two sentences was four seconds of white screen for anyone on a bad network.

A dependency isn't a thing you download. It's a thing you wait for.

If you've done a self-hosting pass recently and felt good about it, here's the five-minute version: open your site, add four seconds of latency to every origin that isn't yours, one at a time, and watch. Not block — stall. The origins that don't matter won't change anything. The one that does will be extremely obvious, and there's a decent chance it's a font.