Web Fonts in WordPress Without Wrecking LCP
How font loading delays rendering, why self-hosting beats a third-party stylesheet, and how to subset, preload and set font-display so text appears immediately.

Fonts are a small part of a page's weight and a disproportionate part of its perceived speed, because text is usually what the visitor is waiting for. When the headline is the Largest Contentful Paint element, font loading is directly on the critical path.
Here's how fonts delay rendering on a WordPress site, and what to do about it.
Why fonts delay rendering
A font isn't discovered until the browser has downloaded and parsed the CSS that references it, then found text on the page that needs it. That's already several steps into the load.
By default, browsers then hide text using that font for a short period while waiting — the flash of invisible text. If the font arrives quickly, nobody notices. If it doesn't, your headline is blank during exactly the window LCP is measuring.
Stack another request in front of that — a stylesheet from a third-party host — and you've added a DNS lookup, a TLS handshake and another round trip before the font file is even requested.
Self-host, always
Loading fonts from a third-party service used to be justified by shared caching: if enough sites used the same host, visitors would already have the font cached. Browsers partition their caches by site now, so that benefit no longer exists.
What remains is the cost: an extra origin to connect to, a stylesheet request before the font request, and a dependency on a service you don't control.
Self-hosting removes all of it. The font is served from your own domain over the connection the browser already has, and you can preload it because you know the URL.
There's also a privacy dimension. Loading fonts from a third party sends your visitors' IP addresses to that service on every page view, which has been found problematic under European data protection rules and is worth avoiding regardless.
Use WOFF2, and only WOFF2
WOFF2 is supported by every browser that matters and is substantially smaller than the alternatives. Serving TTF, EOT and SVG fallbacks alongside it is cargo cult from a decade ago — it just adds files nobody downloads.
If a font is only available as TTF or OTF, convert it. The size difference is large enough to be worth the step.
Subset aggressively
A full font file contains glyphs for scripts your site will never render — Cyrillic, Greek, Vietnamese, extended Latin ranges. Subsetting strips them.
For an English-language site, a Latin subset is often a fraction of the full file's size. If your site includes Devanagari or another Indic script, include that subset specifically rather than shipping everything.
Use unicode-range in your @font-face declarations so the browser downloads a subset only when the page actually contains characters from it. That way a mostly-English page with an occasional Hindi phrase doesn't pay for the Devanagari file on every load.
Limit weights and styles
Each weight and each style is a separate file. Regular, bold, italic and bold italic is four downloads before you've added a light or a semibold.
Most sites need two: a regular and a bold. Check what your design actually uses rather than what the font family offers — it's common to load six weights and use two.
A variable font is worth considering when you genuinely need several weights: one file covers the whole range. It's larger than a single static weight but smaller than four of them, so the maths depends on how many you'd otherwise load.
Never rely on the browser synthesising bold or italic from a regular weight. It looks wrong, and it's a common side effect of loading fewer weights than the CSS asks for.
font-display
This one line in your @font-face rule decides what the visitor sees while the font loads.
- swap — show the fallback immediately, swap when the font arrives. Text is always readable. The cost is a visible reflow when the swap happens.
- optional — show the fallback, and only use the web font if it's available almost immediately; otherwise skip it for this page load. Best for Core Web Vitals, at the cost of some visitors never seeing your font.
- fallback — a compromise: a very short invisible period, then the fallback, with a limited window to swap.
- block — the default-ish behaviour, invisible text while waiting. Avoid it.
- swap is the sensible default for most sites. optional is worth considering when the metrics matter more than typographic consistency.
Preload the critical font
Preloading tells the browser to fetch a font immediately rather than waiting to discover it through the CSS. For the font used in your headline or above-the-fold body text, that removes a whole step from the critical path.
Preload sparingly — one or two files at most. Preloading everything competes with the resources that actually matter and can make things worse.
Self-hosted fonts need the crossorigin attribute on the preload link even though they're same-origin, because fonts are fetched in CORS mode. Omitting it causes the font to be downloaded twice, which is a subtle and common mistake.
Reduce the reflow when the swap happens
With font-display: swap, text renders in a fallback and then re-renders in the web font. If the two fonts have different metrics, the text reflows — which is layout shift, and it counts against CLS.
You can largely eliminate this by defining a fallback whose metrics match: the size-adjust, ascent-override and descent-override descriptors let you tune a system font to line up with your web font closely enough that the swap is barely visible.
It's fiddly to set up and it genuinely works. For a site where the headline is the LCP element, it's the difference between swap being free and swap costing you CLS.
WordPress specifics
- Many themes load fonts from a third-party host by default. Check what your theme enqueues before assuming you're self-hosting.
- Plugins load their own fonts too — icon fonts especially. An icon font loaded for three icons is a poor trade against inline SVG.
- A block theme's theme.json can register self-hosted font families, which is the cleanest way to do it in modern WordPress.
- Check what the block editor loads in the admin as well; editor fonts affect editors' experience even if visitors never see them.
- Serve fonts with long cache headers. They rarely change, and a versioned filename makes long caching safe.
A quick audit
- Open the network panel, filter to fonts, and count the files. More than two or three is worth questioning.
- Check the origin: are they coming from your domain or a third party?
- Check the format: anything other than WOFF2 is probably unnecessary.
- Look at the CSS for font-display. Its absence means invisible text while loading.
- Check whether the LCP element's font is preloaded, and whether the preload has crossorigin set.
- Test on a throttled connection, where all of this becomes visible rather than theoretical.
Frequently asked questions
Should I self-host Google Fonts on WordPress?
Yes. The shared-cache argument for third-party hosting stopped applying once browsers partitioned caches by site, so all that remains is the cost — an extra DNS lookup, connection and stylesheet request before the font is even fetched. Self-hosting also avoids sending visitors' IP addresses to a third party.
What does font-display: swap actually do?
It tells the browser to render text immediately in a fallback font and swap to the web font when it arrives, instead of hiding the text while waiting. Your headline is readable from the first paint. The trade-off is a visible reflow at the swap, which you can minimise by matching the fallback's metrics.
How many font weights should I load?
Usually two — a regular and a bold — because each weight is a separate file. Check what your design actually uses rather than what the family offers; loading six and using two is common. If you genuinely need several, a variable font covers the whole range in one file.
Why is my preloaded font being downloaded twice?
Almost certainly a missing crossorigin attribute on the preload link. Fonts are fetched in CORS mode even from your own domain, so a preload without crossorigin doesn't match the later request and the browser fetches it again. Add crossorigin and the duplicate disappears.
Do web fonts affect Core Web Vitals?
Directly. If your headline is the Largest Contentful Paint element, a font that loads slowly delays LCP — and with the default blocking behaviour the text is invisible while waiting. The swap itself can also cause layout shift, counting against CLS, unless the fallback's metrics are matched to the web font.
Topics
- WordPress fonts
- web font performance
- font-display swap
- self-host Google Fonts