Web fonts are the budget
Eight files for one typeface, and a shared-cache argument that quietly died two years ago.
41%. Not images, which were lazy-loaded and mostly below the fold. Not the framework. Fonts.
Four weights, each with an italic, so eight files, plus a display face for headings which had two more. Ten font files, all requested in the first moments of the page load, all needed before any text could appear in the design’s own typeface.
And of the entire performance conversation on that project, fonts were the one thing I couldn’t get on the agenda, which is the actual subject of this post.
The arithmetic nobody does
Nobody chooses ten font files. What happens is that a designer picks a typeface, and then uses regular and italic in body copy, and semibold for subheadings, and bold for emphasis, and a light weight for the big pull quotes, and every one of those choices is individually defensible and none of them has a number attached at the point it’s made.
Then somebody implements it faithfully, because that’s the job, and the total lands in the browser.
Even in a modern compressed format each of those is somewhere in the tens of kilobytes, and they’re all on the critical path, and until they arrive you’re either looking at nothing or looking at a fallback that’s about to jump.
Nobody chooses ten font files. Ten people choose one thing each, and nobody in the process is holding the total.
Why it’s the protected line item
Here’s the structural bit.
Every other performance decision is a technical one. Cut a script, defer a widget, resize an image, and you’re having a conversation with people who accept that this is your area.
The fonts are the brand. There’s a guidelines document, probably a PDF, that a client paid an agency real money for, and it names the typeface and it names the weights. Cutting one is not a technical change, it’s a request to amend a brand. That goes to the marketing director, who wasn’t in the performance meeting, and who quite reasonably says no.
So the largest avoidable cost on the page is the one with the strongest political protection, and it stays. I’ve been in this conversation maybe a dozen times and I’ve won it twice.
The thing that works, when anything works, is not arguing about the brand at all. It’s loading the site on a throttled phone in front of people, and showing them the three seconds of nothing, and then asking which two of the six weights they’d like to keep. Same move as everything else: show the cost at the moment of the decision.
Self-host, and the argument that died
Before touching a single weight, take the fonts off a third-party service and put them on your own domain. This is nearly free and it’s a straight win.
The historic argument for the hosted services was the shared cache: everybody uses the same URL, so a visitor arriving from another site already has the file. That was genuinely true and it was a good argument.
It stopped being true. Browsers have partitioned their caches by the requesting site, specifically to close a privacy hole where a site could detect what you’d visited based on what loaded quickly. So a font fetched on someone else’s site is now cached under their key and is downloaded again on yours. Everybody’s mental model is about two years out of date, mine included until last year.
What’s left is: an extra origin, an extra DNS lookup and connection before the request even starts, a dependency on somebody else’s uptime, and a privacy question that stopped being theoretical this year when a German court found against a site for passing visitors’ addresses to a font provider without consent.
Self-hosting removes all of that and makes the fonts same-origin, which means they benefit from the connection you’ve already opened. It’s an hour of work.
Subset, cut, and consider a variable
Subset. A lot of font files ship with Cyrillic, Greek, Vietnamese and extended Latin because they’re built for everybody. Most of my clients need Latin and a handful of punctuation. Subsetting routinely halves a file, and unicode-range lets you keep the extended sets in a separate file that only downloads if a character from it actually appears.
Cut weights. The honest test, which I’ve run on people: put two paragraphs side by side, one in 500 and one in 600, and ask which is which. Almost nobody can tell in running text. They can tell in a heading. So you often need the distinction at display sizes and not at body sizes, and that’s two files rather than four.
Italics are the ones I’d defend, incidentally. A synthesized italic, where the browser slants the upright, is ugly and it’s the one place where the compromise is visible to a normal person.
Variable fonts. One file containing the whole weight axis, usually smaller than three static weights, and you get everything in between for free. This is the right answer for anything using three or more weights, and it’s the wrong answer if you only use one, because a variable font is larger than a single static cut.
The descriptors that make the swap invisible
The best new thing in this area, and it addresses the complaint I had back in 2015 when the choice was between text you can’t read and text that jumps.
font-display: swap shows a fallback immediately and swaps when the real font arrives, which is the right trade for readability and produces a visible reflow, because your typeface and Arial have different metrics.
You can now correct for that. The @font-face descriptors let you adjust a local fallback so its metrics match your web font closely enough that the swap doesn’t move anything:
@font-face {
font-family: "Brand Fallback";
src: local("Arial");
size-adjust: 97%; /* tuned until the two overlay */
ascent-override: 92%;
descent-override: 24%;
line-gap-override: 0%;
}
body { font-family: "Brand", "Brand Fallback", sans-serif; }
Getting the numbers right is fiddly and there are tools that generate them. The payoff is that the fallback occupies the same space as the real thing, so the swap is a change of shape rather than a change of layout, and the layout-shift number that everyone’s been reporting on since 2020 stops being about fonts.
The counter, which I half accept
There’s a position that says use the system font stack and be done, and I don’t hold it, though I’ve drifted toward it.
Type does matter. A site set in the default sans is not neutral, it’s characterless, and a client who has paid for a brand is entitled to have that brand rendered. The system stack is excellent for interface text, buttons and labels and tables, where you want it to look native and nobody’s admiring the type. It’s less satisfying as the voice of an organization.
Where I’ve landed is: one family, two or three weights, self-hosted, subset, with a metric-matched fallback, and the display face used only for display. That’ll be somewhere around 60 to 90 KB rather than 400, and nobody in the brand meeting has to concede anything they’d notice.
Ten files to three
Got that site from ten files to three. The client didn’t spot the difference and the page went from about four seconds to first text on a throttled phone to under one and a half.
I did have to sit in a meeting and be told the light weight was non-negotiable, which it was. It’s on one page, on one heading, it’s loading with swap, and it’s fine.