Skip to content
Strynal, Digital Agency

Engineering 7 min read

Loading Web Fonts Without Wrecking Performance

Web fonts are easy to add and easy to get wrong. A practitioner guide to self-hosting, subsetting, font-display, preload, and ending the layout shift fonts cause.

By Strynal Team

A web font is one of the few assets that can make a page slower and shift its layout at the same time. It loads late, holds back your text, then reflows the page when it finally arrives. Treated casually, the typography you chose to look considered becomes the thing that fails your performance budget.

Most teams add a font in two lines, see it render in development on a fast connection, and never look again. The cost shows up later, on a mid-range phone over a tired network, as blank text and a paragraph that jumps half a line when the real font swaps in. None of that is necessary. The fixes are well understood and cheap to apply.

Why fonts are a performance problem

Three costs, and they stack.

Text waits on the font. The browser finds your font request inside CSS, which it can’t load until it has parsed the stylesheet, which it can’t parse until that file arrives. So the font sits deep in the dependency chain. Until it resolves, the browser either hides the text or paints a fallback. The first behavior is a Flash of Invisible Text. The second is a Flash of Unstyled Text. Both are visible to your visitor.

The swap shifts the layout. Your fallback font and your web font almost never share the same width and height per character. When the real font replaces the fallback, line lengths and line heights change, and everything below moves. That movement is Cumulative Layout Shift, one of the three Core Web Vitals that decide whether a page feels fast, and font swap is one of its most common causes.

The bytes are real. A single weight of a full font is often 30 to 100 KB. Four weights across two families, in formats you don’t need, on a third-party origin that needs its own connection, and you have added a quarter of a megabyte and two network round trips before a word is styled.

A font that shifts the page is a bug, not a style choice.

The loading lifecycle, in plain terms

To fix font performance you have to know what the browser actually does. It runs in order: discover the @font-face rule, decide whether anything on the page needs that font, request the file, then swap the rendered text once the file lands. Every optimization below targets one of those steps. You either move the discovery earlier, shrink the file, or make the swap invisible.

font-display: choose the behavior on purpose

The font-display descriptor tells the browser what to paint while the font loads. The default leans toward hiding text, which is the worst outcome for perceived speed. Set it deliberately.

  • swap shows the fallback immediately, then swaps to the web font whenever it arrives. Text is never invisible. The trade-off is a guaranteed swap, and therefore a layout shift unless you match metrics (next section).
  • optional gives the font a tiny window. If it isn’t ready almost instantly, the browser keeps the fallback for the whole page view and quietly caches the font for next time. No shift, no blank text, at the price of some first-time visitors never seeing your brand font.
  • block hides text for up to three seconds. Avoid it for body copy.

My default for body text is swap paired with matched fallback metrics, so the page is readable at once and the swap is invisible. For a font that is purely decorative, such as a display face used in one heading, optional is the safer call: if it loses the race, nobody notices, and you have spent nothing.

Self-host your fonts

Loading fonts from a third-party service such as the hosted Google Fonts CSS endpoint costs you a connection to a second origin, a DNS lookup, and a TLS handshake before the font request even starts. That is two slow steps on the critical path for no benefit you can’t get yourself.

Download the files, serve them from your own domain, and write your own @font-face rules. You get one origin, full control over font-display, the ability to preload, and a privacy posture that doesn’t hand visitor IP addresses to a third party on every page load. Self-hosting is the single highest-leverage change for most sites still pulling fonts from an external stylesheet.

Subset and ship woff2 only

A font file you download contains glyphs for languages your site will never render. Subsetting strips the file down to the characters you actually use. For most Latin sites, a Latin subset removes a large share of the weight before you do anything else. If your copy is fixed, you can subset to the exact character set and go further still. This is the same discipline that pays off across your asset pipeline, the way trimming and serving images right does.

On format, the answer is settled: ship woff2 and nothing else. Every browser you care about supports it, and it compresses better than every older format. Serving ttf, eot, or woff alongside it is dead weight from a compatibility table that expired years ago.

Preload the fonts that paint first

@font-face is lazy by design: the browser only fetches the font once it finds an element that needs it, which is late. For the one or two fonts used in content above the fold, override that with a preload hint in the document head:

<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>

Preload moves discovery to the very start of the load, so the font downloads in parallel with your CSS instead of after it. Two rules keep this from backfiring. Preload only the fonts genuinely needed for the first paint, because preloading everything just clogs the same pipe you were trying to clear. And always include crossorigin, even for same-origin fonts, or the browser fetches the file twice.

Match fallback metrics to end the shift

This is the step most teams skip, and it is the one that turns a visible swap into an invisible one. The shift happens because your fallback and your web font occupy different space per character. CSS lets you correct the fallback so it occupies the same space.

Inside an @font-face rule that defines your fallback, four descriptors do the work: size-adjust, ascent-override, descent-override, and line-gap-override. Tuned correctly, the fallback renders at the same effective size and line height as the real font. When the web font swaps in, nothing moves, because the box was already the right shape.

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial");
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

You then list Inter, "Inter Fallback", sans-serif so the adjusted fallback sits between them. Tools exist to compute these numbers for a given font pair, and some frameworks generate the fallback face for you automatically. Either way, the result is swap with zero layout shift, which is the combination you actually want.

Set a font budget and hold it

Performance is a budget problem before it is a technique problem. Decide the limits up front and design within them.

  1. Families. One or two. A text face and, if the brand calls for it, a display face. A third family is usually indecision, not design.
  2. Weights. Ship the weights you use, not the weights the foundry offers. Three or four total is plenty for most sites. Every extra weight is another file on the wire.
  3. Total bytes. Hold all fonts together to a clear ceiling, often around 100 KB after subsetting. If you blow past it, a variable font may help.

A variable font packs a full weight range into one file, so it can beat shipping four static weights once you use three or more. Below that, a couple of static weights are usually smaller. It is a trade-off to measure, not a default. All of this is easier when type decisions live inside a real typographic system rather than being chosen file by file.

How Strynal approaches web font performance

We treat type as both a brand decision and an engineering one, because it is both. As a boutique studio, the senior team that picks the typeface is the team that wires it into the build, so the weight count, the subset, the font-display value, and the matched fallback are settled together rather than discovered as a regression after launch. There is no handoff where speed quietly gets traded for a fourth weight nobody needed.

The standard is plain: brand-correct typography that paints instantly, reads at once, and never moves the page. That discipline is part of how we build under our websites and apps practice, and it is the kind of detail that separates a site that merely looks considered from one that behaves that way.

If your fonts are slowing pages down or shifting your layout, that is a focused, fixable problem, and a good first thing to look at together.