← Back to blog

Font Loading Strategy: Preload for LCP, Match Metrics for CLS

October 11, 2026
Font Loading Strategy: Preload for LCP, Match Metrics for CLS

Self-host WOFF2 fonts, subset them aggressively, preload only the one or two files your above-the-fold content needs with crossorigin set, and split your font-display choice by role: optional for body copy, swap paired with metric overrides for brand-critical headings. Verify every change with Lighthouse, WebPageTest, and Chrome DevTools before shipping it.


TL;DR:

  • Use WOFF2 unless legacy support is required, choose variable fonts for three or more weights, and subset glyphs to keep font payload under 100 KB.
  • Set body text to optional to avoid late layout shifts; reserve swap for headings where brand accuracy matters, and match fallback metrics before shipping.
  • Preload only one or two fonts used above the fold, ensure the URL exactly matches the font declaration, and include crossorigin to prevent duplicate downloads.
  • Self host font files and cache them for one year with immutable headers; serve them through a CDN supporting HTTP/2 or HTTP/3.
  • Change one setting at a time, compare Lighthouse and WebPageTest results with a baseline, then verify improvements under throttled conditions and in field data.

Forge-web-studio
Build a Faster, Clearer Website
Forge Web Studio creates strategy-led websites for Texas businesses, with a focus on mobile clarity, effective lead flow, and local search optimization.
Explore Forge Web Studio

Table of Contents

Why Fonts Make or Break Core Web Vitals

A web font only starts downloading after the browser discovers it, and that discovery usually waits on CSSOM construction. The stylesheet has to parse before the browser even knows a @font-face rule exists, which pushes the font request well behind the HTML and CSS themselves. When that text sits in your hero or above the fold, the delay shows up directly in Largest Contentful Paint.

Layout shift is the second failure mode. A fallback font with different metrics renders at a different width and height than the web font that replaces it, and that swap triggers Cumulative Layout Shift the moment the real font arrives.

A layered checklist keeps both problems in check:

  • Choose WOFF2 and trim the character set to what the page actually uses.
  • Decide delivery: self-host with long cache headers rather than relying on a third-party host.
  • Preload only the fonts your above-the-fold content needs.
  • Pick font-display per role, not globally.
  • Match fallback metrics when you use swap so the swap doesn't shift layout.
  • Re-test with Lighthouse and WebPageTest after each change, not at the end.

FCP and LCP react to download and discovery timing. CLS reacts to metric mismatches during the swap. Treat them as separate problems with separate fixes.

Choosing Formats, Variable Fonts, and Subsetting to Cut Payload

WOFF2 should be your only format in 2026 unless you have a documented legacy-browser requirement. It compresses better than WOFF or TTF and every modern browser supports it, so shipping multiple formats just adds dead weight to the request.

Variable fonts earn their place once you need three or more weights or styles. A single variable file replacing four or five static weights usually nets a smaller total payload and cuts the number of HTTP requests, which matters more on mobile networks with high round-trip latency. Below that threshold, two or three static WOFF2 files are often lighter than one variable file carrying the full weight axis.

Subsetting removes glyphs you never render. Two approaches cover most projects:

  • Use unicode-range in your @font-face declarations to split a font by language or script so the browser only fetches what the page needs.
  • Build-time tools like glyphhanger or pyftsubset strip unused glyphs before the font ever ships, based on the actual characters in your content.

Aim to keep your total font payload well under 100 KB per page where possible. A single unsubset WOFF2 with a full Latin character set plus symbols can easily blow past that on its own.

Pro Tip: Run glyphhanger against your rendered HTML, not your source files, so dynamically injected text doesn't get stripped by accident.

Font-Display: Matching the Value to the Role

font-display controls two timers: a block period where text stays invisible while the web font downloads, and a swap period where the browser keeps checking for the font and swaps it in when ready. MDN's font-display reference lays out the four values precisely: block gives a short block and infinite swap, swap gives almost no block and infinite swap, fallback gives a short block and a short swap window, and optional gives a short block (about 100 milliseconds) with no swap at all if the font isn't ready in time.

That distinction maps cleanly onto role:

  • Use optional for body text, where a layout shift from a late swap costs more in CLS than the branding loss costs in visual polish.
  • Use swap for brand-critical headings, but only when you pair it with metric overrides so the swap doesn't move anything on the page.
  • Use fallback as a middle ground when you want a brief chance at the real font without the open-ended swap window swap allows.

The Web Perf Clinic guide frames this as a 2026 best-practice pattern: optional protects stability for most text, swap plus overrides protects branding where it matters, and nothing forces a binary choice across the whole page. Letting one global font-display value govern every font on the page is the most common way teams leave CLS points on the table.

Preload Rules and the Crossorigin Requirement

A font preload tag looks simple but fails silently when it's wrong:

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

The href has to match the @font-face src byte for byte, including the subset file if you're serving one. A mismatch means the browser fetches two files instead of reusing the preloaded one.

crossorigin is not optional. Web are explicit that fonts are treated as CORS-required resources: without crossorigin, a preloaded font gets discarded and re-fetched anonymously, which doubles the download instead of saving time.

Keep preloads limited to the one or two fonts your LCP candidate actually uses above the fold. Every additional preload competes with other high-priority requests, including the LCP image or the main CSS file, for the same early bandwidth. For third-party font origins you're not preloading individual files from, use preconnect instead to warm up the DNS and TLS handshake without forcing a full download.

Font preload timing and bandwidth priorities

Pro Tip: Check your network waterfall in WebPageTest after adding a preload. If the preloaded font isn't the one that paints your LCP element, remove it.

Metric Overrides: Eliminating CLS When You Use Swap

When swap replaces a fallback font with your web font, any difference in ascent, descent, or line-gap moves surrounding content. The @font-face descriptors ascent-override, descent-override, and line-gap-override let you force the fallback font's metrics to match the web font's, so the swap changes the glyphs without changing the box the text occupies.

MDN's size-adjust documentation describes a related descriptor that scales glyph outlines and metrics on the fallback font to align with the web font's geometry, which is often the piece that gets the visual match close enough that the override values alone can't manage.

  • Start from the web font's own metrics table, not guesswork.
  • Use a build-time tool rather than computing values by hand for every font and weight.
  • Test the result on Windows, macOS, Android, and iOS, since fallback fonts differ by platform and the same override can look perfect on one OS and slightly off on another.
  • Reserve the effort for hero headings and brand-critical text; body copy running font-display: optional doesn't need it.

Chrome's font fallback guide points out that calculating these values manually is slow and error-prone, and recommends build-time tools like Fontaine or framework plugins such as next/font or @nuxtjs/fontaine that compute and inject matched fallbacks automatically.

Pro Tip: Keep a small table of your brand font's metrics alongside the generated override values in your repo, so a font swap months later doesn't require redoing the whole calculation.

Delivery: Self-Hosting, Caching, and CDN Choices

Self-hosting removes a DNS lookup and a separate TLS handshake to a third-party font host, and web.dev's guidance notes it also hands you control over caching and compression that you don't get from an external provider. That control matters: set Cache-Control: max-age=31536000, immutable on your font files so repeat visits skip the network entirely, and serve them from a CDN that supports HTTP/2 or HTTP/3 for faster parallel delivery.

Inlining the critical @font-face declarations directly in your HTML's <head>, rather than loading them from an external stylesheet, speeds up discovery because the browser doesn't have to wait on a second file to learn the font exists. The trade-off against preload is worth noting: web.dev flags that preload can bypass unicode-range negotiation and force a full font download even when the page only needs a subset, while inline @font-face lets the browser's own negotiation decide.

  • Self-host primary fonts and set long-lived immutable cache headers.
  • Serve over a CDN with HTTP/2 or HTTP/3.
  • Inline critical @font-face rules for above-the-fold text.
  • Use unicode-range splits so sections of a page that don't need certain glyphs never trigger their download.

For a broader look at trimming other render-blocking assets alongside fonts, see this image optimization guide, and for a deeper technical breakdown of speed audits generally, this developer's performance guide covers the surrounding measurement work well.

A Test Plan You Can Repeat on Every Project

Measuring correctly matters as much as the fixes themselves.

  1. Capture a baseline with Lighthouse and WebPageTest, recording LCP, CLS, FCP, and the font request timing from the network panel.
  2. Change exactly one variable at a time: subset the font, add a preload, or apply a metric override, never all three in the same pass.
  3. Re-run the same lab tests after each change and compare against the baseline, then confirm the trend holds in field data (RUM) once deployed.
  4. For scripted or advanced sequences, use document.fonts.ready and the CSS Font Loading API to detect exactly when a font becomes available rather than guessing from visual inspection.
  5. Read the network waterfall and filmstrip together: a font request that starts late but finishes before paint rarely matters, while one that finishes after the LCP timestamp is the one to fix next.

This sequence is the same one covered in more depth in our Core Web Vitals workflow guide, which walks through prioritizing INP alongside LCP and CLS.

How We Apply This Pattern on Client Builds

Every site we build starts mobile-first, which means font payload gets scrutinized before desktop styling even enters the conversation. We default to subsetted WOFF2, preload the single headline font a given template needs, and apply font-display: optional to body text so a slow connection never costs a layout shift. You can see the pattern in practice across our demo gallery, and our website audit example shows how we report these metrics back to a client in plain terms.

What the Font-Loading Conversation Usually Gets Wrong

Most advice on this topic treats font-display: swap as the default answer, and that's backwards for the majority of body text on a commercial site. Swap optimizes for showing your exact brand font eventually, but it does that by accepting a layout shift that a visitor on a slow connection feels directly. Optional is the boring choice that actually protects the metric Google scores you on.

The second thing the conventional advice underweights is testing discipline. Developers will preload a font, glance at a single Lighthouse run, and move on. A preload that helps on a cable connection can easily hurt on the exact mid-tier Android and 4G profile that Core Web Vitals field data weighs most heavily, and you only catch that by testing under a throttled profile, not just your local fiber connection.

If you take one thing from this: stop treating font-display as a single site-wide setting. Split it by role, protect body text with optional, and spend the metric-override effort only where branding genuinely needs swap. Everything else here, preloading, subsetting, self-hosting, is in service of that one decision.

— Dylan

Get Your Font Loading and Site Speed Fixed Without the Guesswork

Diagnosing font-related layout shifts and slow LCP takes a Lighthouse run, a WebPageTest trace, and the patience to fix one variable at a time, work most business owners don't have hours to spare for. Sites are built with subsetted WOFF2 fonts, role-specific font-display rules, and metric overrides applied early in the process rather than after complaints about shifting headlines.

Forge-web-studio

If your current site is showing poor Core Web Vitals scores or visible text jumps on load, a Website Redesign addresses it alongside the rest of your front-end. Pricing for a full rebuild starts with our Starter Website package at $500, and every project includes direct access to the founder handling your build, not a account manager relaying requests to a dev team you never talk to.

FAQ

What is the best font loading strategy for Core Web Vitals?

Self-host subsetted WOFF2 fonts, preload only the one or two fonts needed above the fold with crossorigin set, and use font-display: optional for body text while reserving swap with metric overrides for branded headings. This combination, described in web.dev's font guidance, targets LCP and CLS at the same time instead of trading one for the other.

Should I use font-display swap or optional?

Use optional for most body text because it avoids the layout shift a late font swap causes, and use swap only for headings where brand accuracy matters enough to accept that risk. MDN's font-display documentation confirms optional gives a short block period and no swap, while swap swaps indefinitely once the font loads.

Why do I need crossorigin on a preloaded font?

Browsers treat fonts as CORS-required resources, so a preload without crossorigin gets fetched anonymously and then discarded when the actual @font-face request runs, forcing a second download. This is called out directly in web.dev's preload guidance as one of the most common preload mistakes.

How do I stop fonts from causing layout shift?

Apply ascent-override, descent-override, line-gap-override, and size-adjust to your fallback font so its box matches your web font's geometry before the swap happens. MDN's size-adjust reference explains how this scaling aligns fallback and web font metrics to reduce the shift.

Are variable fonts better than static font files?

Variable fonts tend to win once a page needs three or more weights or styles, since one file replaces several static requests and often reduces total payload. Below that, a couple of subsetted static WOFF2 files can be lighter, so the right choice depends on how many weights your design actually uses.

Sources