Open a site that scores 100 on mobile PageSpeed. Throttle your network to slow 3G in Chrome DevTools. Hard reload. Watch what happens in the first second.

You will almost certainly see the page render in a system default font, hold there for a fraction of a second, then swap to a custom font as it finishes loading. The swap is sometimes obvious (a serif replaced by a sans-serif) and sometimes subtle (a serif replaced by a slightly different serif with different metrics). Either way, it is visible to a careful viewer.

This is FOUT. Flash of Unstyled Text. The term was coined in 2009 by Paul Irish, then a Google Chrome developer, to describe the brief period when text renders in fallback fonts before custom fonts load. Sixteen years later, FOUT is the recommended pattern in every major web performance guide. Web.dev tells you to ship it. Lighthouse rewards you for shipping it. The Web Almanac annual report cites it as best practice.

The metrics back the consensus. CLS does not flag the swap. LCP does not penalize it. The Performance score stays at 100. From the auditor’s perspective, the site is correct.

From the user’s perspective, the site has a small visible glitch on every fresh load.

How we ended up here

The four standard font-display values control what happens during the gap between page parse and font arrival.

font-display: block hides text until the custom font arrives. No flash, but the page shows blank space for up to three seconds during the font load. This destroys First Contentful Paint and Largest Contentful Paint. Lighthouse hates it.

font-display: swap renders text immediately in the fallback font, then swaps to the custom font when it arrives. Visible flash, but text is readable from the first frame. Lighthouse loves it.

font-display: optional renders text immediately in the fallback, gives the custom font a hundred milliseconds to arrive, and if it misses that window the fallback stays for the rest of the session. Less visible flash, but some users see fallback fonts permanently on slow connections.

font-display: fallback is a compromise nobody actually uses.

The performance industry settled on font-display: swap as the default sometime around 2018, and the recommendation has not meaningfully changed since. The reasoning is sound: showing readable text in a default font is better than showing blank space. Visible content is preferable to invisible content. The flash is the price of the speed.

This is true for the binary choice between block and swap. It misses that there is a third option that produces no flash and no blank space, and the only reason it is not the default is that it requires more setup than copy-pasting a Google Fonts link tag.

The fix the score does not require

The pattern that eliminates FOUT without sacrificing the score is self-hosting plus preload plus metric-matched fallback fonts. It is three layers and each one does specific work.

Self-hosting means downloading the WOFF2 files from Google Fonts and serving them from your own domain. This is allowed under the SIL Open Font License that covers most fonts on Google Fonts. The legal layer is a non-issue. The performance benefits are real: you eliminate two DNS lookups (one to fonts.googleapis.com, one to fonts.gstatic.com), you eliminate the round trip required to discover the font URLs from the CSS, and you can subset the fonts to drop scripts you do not use. For an English-only site loading a font like DM Sans with default Google Fonts subsetting, the file is around 30 KB. Subset down to Latin Basic only and you can land at 8 to 12 KB.

Preload means adding a link rel=”preload” tag in the document head pointing directly to the font file. This tells the browser to start the font download as soon as the HTML is parsed, in parallel with the rest of the page. By the time the CSS is applied and the browser knows it needs the font, the font is already in the cache.

Metric-matched fallback means defining a CSS @font-face block for a fallback font (like Arial or system-ui) with size-adjust, ascent-override, descent-override, and line-gap-override set to values that make the fallback render at the same dimensions as your custom font. When the swap eventually happens, no characters move. The text just sharpens into the real font. The visible glitch becomes imperceptible because nothing actually shifts.

The values for the override properties can be generated for any font pair using fontpie.com or Malte Ubl’s font-style-matcher tool at meowni.ca/font-style-matcher. Both are free, both take about thirty seconds.

The total time to implement this on a site that currently uses Google Fonts CDN is roughly thirty minutes. The first time. After you have done it once and saved the pattern, every subsequent build is closer to five minutes.

Why the standard guides do not teach this

The cynical answer is that the standard guides are written by people who optimize for the score, the score does not penalize FOUT, and they have no incentive to chase a fix the metric does not require.

The charitable answer is that the metric-matched fallback technique only became browser-supported in 2022 and 2023. The size-adjust property landed in Chrome 92, Firefox 89, and Safari 17. Many performance guides written before then simply do not mention it because it did not exist as a viable option. The guides have not been universally updated. The cycle continues.

Both answers are partly true. The result is the same. Most modern sites ship FOUT because the recommended pattern produces FOUT, and most developers stop at the recommended pattern.

The deeper pattern worth naming

FOUT is one example of a broader phenomenon: the metrics-driven culture of web performance has produced a generation of optimized sites that look polished on Lighthouse and rough in real-world use.

Lazy-loaded images that pop in jarringly as you scroll. Aggressive code-splitting that produces visible re-renders during navigation. Async analytics that fire late and miss page-leave events. Skeleton screens that flash for two hundred milliseconds even when the content is already cached. None of these are penalized by any Lighthouse metric. All of them are noticed by attentive users.

This is Goodhart’s Law applied to web development. A measure becomes a target, the target becomes the design, the design produces side effects the measure does not see. The measurement was supposed to be a proxy for user experience. Once it became the goal, the proxy and the experience drifted apart.

The right way to think about performance, and this is the framing I use on every build, is to optimize for real user experience and let the metrics follow. Most of the time they will. When they conflict, choose the user experience answer. The metric is wrong more often than the metric admits.

Where I had to take my own advice

I should mention: the site I was just bragging about scoring 100 on mobile has FOUT on first load. I shipped it that way. I noticed it. I left it because the launch deadline was tight and the client had not flagged it. The retainer engages this month, and the FOUT fix is the first item in the polish pass.

This is the small grey-hat tip of the hat. It is fine to ship the glitch when the metric does not penalize you and the client does not see it. The mistake is leaving it shipped. Anyone who knows performance well enough to be impressed by the score also knows enough to spot FOUT. Sophisticated prospects evaluating your work will see it. They will quietly file it under “decent but not buttoned up.” That is the kind of small signal that costs you the deal at the margin without ever getting named in the rejection.

The fix on the live site takes thirty minutes once the retainer kicks in. The lesson is that the score said it was fine is not the same as it was fine. The score is a tool. The polish is the product.

The honest framing for clients

If you are pitching performance work to a client and you want to differentiate from agencies who promise the score and ship the standard FOUT pattern, the language goes like this:

The Lighthouse score measures specific things. Real user experience is broader than what the score measures. We optimize for both. Here is what that means in practice: 100 on mobile PageSpeed plus self-hosted fonts plus metric-matched fallbacks plus deferred analytics that does not fire until the page is interactive. Each piece is invisible individually. Together they produce a site that feels right in a way most fast sites do not.

That paragraph beats every generic “we make fast websites” pitch I have ever seen. It is also true.

For more on the wider performance approach, see the breakdown of the 100/100 mobile site this article references.