Blog · Guides

Web Font Optimization for WordPress: Stop Fonts from Blocking Paint

Custom fonts make a site feel designed rather than default, but they are also a common cause of slow paint times and layout shift. If your Largest Contentful Paint (LCP) is creeping past the 2.5 second threshold, or your Cumulative Layout Shift (CLS) score is jumping around, your fonts are a reasonable first place to look. Here is how to load them without punishing your Core Web Vitals.

Why fonts hurt performance in the first place

Every font file is a network request. If that request goes to a third-party domain (Google Fonts, Adobe Fonts, a CDN), the browser has to resolve DNS, open a new connection, and negotiate TLS before it can even start downloading the file. That round trip alone can cost hundreds of milliseconds on a mobile connection, and it happens before the browser can render any text styled with that font.

Layout shift comes from a different mechanism. If your fallback system font and your custom web font have different metrics (character width, line height, x-height), the page renders once with the fallback, then reflows when the custom font finishes loading and swaps in. That reflow is exactly what CLS measures, and it is often the single biggest contributor to a bad score on text-heavy pages.

Self-host your fonts

The single highest-leverage change is to stop calling out to a third-party font host and serve the files from your own domain instead. This removes the extra DNS lookup and connection setup, and it lets the font be cached and preloaded alongside the rest of your assets.

To self-host:

  1. Download the WOFF2 files for the weights and styles you actually use (most sites need two or three, not the full family).
  2. Upload them to your theme, typically in a fonts directory, or through the Media Library if your workflow prefers that.
  3. Declare them with @font-face in your CSS.
@font-face {
  font-family: 'Inter';
  src: url('/wp-content/themes/your-theme/fonts/inter-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

If you are using a page builder or theme that pulls fonts from Google Fonts by default, check its settings first. Many modern WordPress themes now include a “host fonts locally” toggle that does this automatically.

Only load the weights and characters you need

Full font families often ship with hundreds of weights, styles, and language character sets you will never use. Subsetting strips a font down to just the glyphs your site actually needs, usually Latin characters plus whatever punctuation and symbols show up in your content. A subsetted WOFF2 file can be a fraction of the size of the full family file.

Tools for subsetting include fonttools (specifically its pyftsubset utility) run from the command line, or the subsetting options built into some font foundries’ download tools. Google Fonts itself serves subset files automatically when you request specific character ranges through its API, though self-hosting still avoids the third-party request entirely.

A practical rule: if your site is English-only, request the Latin subset. If you need accented characters for other languages, add the Latin Extended set. Skip Cyrillic, Greek, or CJK character sets unless your content actually uses them.

Use font-display to control the loading behavior

The font-display property in your @font-face rule tells the browser what to do while the font file is still loading. For most sites, swap is the right choice: it renders text immediately in a fallback font, then swaps to the custom font once it arrives. The alternative, font-display: block, hides the text entirely for a short window, which can hurt LCP if your headline uses that font.

@font-face {
  font-family: 'Inter';
  src: url('/wp-content/themes/your-theme/fonts/inter-regular.woff2') format('woff2');
  font-display: swap;
}

Preload the fonts that matter for LCP

If your custom font is used in above-the-fold text, especially anything that qualifies as your page’s LCP element, preload it so the browser fetches it early instead of waiting to discover it deep in your CSS.

<link rel="preload" href="/wp-content/themes/your-theme/fonts/inter-regular.woff2" as="font" type="font/woff2" crossorigin>

Add this inside your theme’s <head>, typically via functions.php using wp_head, or through your caching plugin if it offers a font preload option. Do not preload every weight and style you own, only the one or two files needed for visible, above-the-fold text. Preloading fonts you do not need immediately competes with other critical requests and can slow things down instead of speeding them up.

Prevent layout shift with matched fallback metrics

Even with swap and preload in place, a font swap can still cause a visible reflow if the fallback font and the custom font have very different proportions. Modern CSS gives you a way to close that gap using descriptors like size-adjust, ascent-override, and descent-override in a fallback @font-face declaration, effectively resizing a system font to match your custom font’s metrics so the swap is nearly invisible.

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
}

body {
  font-family: 'Inter', 'Inter Fallback', sans-serif;
}

Getting the exact percentages right takes some trial and error with a tool that compares font metrics, but even an approximate match reduces visible jank meaningfully.

Let caching handle delivery once the fonts are optimized

Once your fonts are self-hosted, subsetted, and preloaded correctly, your caching layer takes care of the rest. On a LiteSpeed server, LiteSpeed Cache serves the full page (fonts included) from server-side cache, and it includes options to preload critical CSS and fonts automatically. Sitting Cloudflare in front adds edge caching for static assets like font files, so repeat visits and visitors far from your origin server both benefit. If you are on a host like ServerBorn, this LiteSpeed and Redis stack is already tuned to work with self-hosted, preloaded fonts out of the box, so the plugin-level work above is usually all that is needed.

Measuring the improvement

After making changes, check both lab and field data. Run PageSpeed Insights or Lighthouse for a lab test of LCP, INP, and CLS under controlled conditions, then check the Core Web Vitals report in Google Search Console for real-world field data from actual visitors. The two can disagree, especially on CLS, since field data reflects a much wider range of devices and connection speeds than a single lab run.

Takeaway

Fonts are an easy thing to overlook because they feel like a design decision, not a performance one. Self-hosting removes the third-party connection cost, subsetting shrinks the files you do load, font-display: swap keeps text visible while fonts load, preloading gets critical fonts moving early, and fallback metric matching stops the visual jump when the swap happens. Put those together and fonts stop being a hidden tax on your Core Web Vitals.