OMAR
Field NotesCV
Self-Host Your Fonts. All of Them.
← All Notes
Performance05 April 2026 · 5 min read

Self-Host Your Fonts. All of Them.

Two link tags to a font CDN cost you a DNS lookup, a connection, a redirect chain, and a layout shift — on the most visible text on the page.

Two link tags to a font CDN look free. They are not. They cost a DNS lookup, a TLS handshake to a third party, a stylesheet round trip, and then — only then — the font files themselves. All of that happens before the most visible text on your page can render in the right typeface.

Self-hosting removes the entire chain. It took me an afternoon on this site and it is now the first thing I change on a client project.

What the CDN actually costs you

The sequence on a cold visit looks like this: resolve the CDN domain, connect to it, download a CSS file, discover the font URLs inside it, resolve that domain, connect again, download the fonts. Two connections deep before a single glyph exists.

None of that is cached across sites any more, either. Browsers partition their caches by origin now specifically to prevent cross-site tracking, so the old argument that "the user probably already has it" has not been true for years.

The switch is boring, which is the point

Install the families as packages, import them, delete the link tags.

npm i @fontsource/anton @fontsource/space-grotesk @fontsource/tajawal
import '@fontsource/anton/400.css'
import '@fontsource/space-grotesk/400.css'
import '@fontsource/space-grotesk/700.css'
import '@fontsource/tajawal/400.css'
import './theme.css'

Your bundler now fingerprints the font files and serves them from your own origin, which means they inherit your caching headers:

/assets/*
  Cache-Control: public, max-age=31536000, immutable

That is a genuinely permanent cache, which the CDN cannot give you because its URLs are shared and its policy is not yours.

Import only the weights you use

This is where the win is either real or imaginary. A family with nine weights in two styles is a lot of files, and importing all of them is worse than the CDN you just left.

Audit the design first. This site uses four faces in total: one display weight, three body weights, one pixel face, plus the Arabic display and body faces. Everything else was never referenced by any rule.

If a weight does not appear in your CSS, do not import it. Variable fonts change this calculation — one file covering a range can beat three static cuts — but only if you actually use the range.

Subset for the languages you serve

The Latin subset of a typical face is a fraction of the full file. Cyrillic, Greek and Vietnamese ranges ship by default in many packages and are pure waste on a site that serves English and Arabic.

Most font packages expose per-subset CSS files so you can import only the ranges you need. On a bilingual site this matters twice over: the Arabic faces are considerably larger than the Latin ones, and shipping unused Latin extensions alongside them adds up quickly.

Preload the one face that blocks the fold

Not all of them — preloading everything is the same as prioritising nothing.

<link rel="preload" as="font" type="font/woff2"
      href="/assets/anton-latin-400.woff2" crossorigin>

The crossorigin attribute is required even for same-origin fonts. Without it the browser fetches the file twice, which is a very annoying way to make things slower while trying to make them faster.

Always set font-display

@font-face {
  font-family: "Anton";
  src: url(...) format("woff2");
  font-display: swap;
}

swap renders immediately in the fallback and switches when the real face arrives. You accept a visible flash of substituted text in exchange for never showing a blank page. On a portfolio or a marketing site that trade is right; the content being readable is worth more than one moment of typographic imperfection.

To reduce the flash, pick a fallback with similar metrics and declare it in the stack rather than letting the browser pick something wildly different.

The privacy argument

Every request to a third-party font host tells that host who visited your page, from where, on what. For a site with European visitors this has been the subject of real legal decisions, and for a site handling client work it is a question you do not want to have to answer.

Self-hosting makes the question disappear entirely, which is worth something on its own.

Fonts are the only asset on most pages that blocks the appearance of the text people came to read. They deserve more attention than they usually get.

The five-minute version

Install the families locally. Import only the weights and subsets you use. Preload the single face above the fold, with crossorigin. Set font-display: swap. Cache immutably. Delete the third-party link tags and never look back.

FontsPerformancePrivacy

Need this built properly?

I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.

Keep Reading