Technical SEO

Font Optimization for SEO: Eliminate CLS, Improve LCP, and Stop Invisible Text

By Reviewed by Hawrry Bhattarai
August 4, 2026 10 min read
Contents
TL;DR — the short answer

Web fonts cause CLS and invisible text that tank your Core Web Vitals. Here's how to use font-display, preload, variable fonts, and system stacks to fix both issues.

11 min read · Technical SEO · Last updated July 2026

Quick answer: Web fonts create two problems: invisible text (FOIT) while loading, and layout shift (CLS) when swapping to the loaded font. Fix FOIT with font-display: swap, fix CLS by using size-adjust to match fallback font metrics, and eliminate both problems with preload hints for your critical font files.

Introduction

Every web font you load adds at least one HTTP request, a render-blocking moment, and a potential CLS event. Used thoughtlessly, a site with two Google Fonts families can add 400ms to its LCP and a CLS score of 0.25 — failing Google’s threshold by 150%.

The good news: font optimization is almost entirely configuration. You don’t need to redesign your site or switch to ugly system fonts. You need font-display: swap, a preload hint, and for the perfectionist: size-adjust to make your fallback font dimensionally identical to your web font.

What you’ll learn:
– Why fonts cause both FOIT and CLS — and how these are different problems
– The five font-display strategies and when to use each
– How to preload fonts correctly without browser warnings
– Variable fonts and why they reduce network requests
– System font stack for zero-latency body text


Table of Contents

  1. How Web Fonts Hurt Performance
  2. The Five font-display Values
  3. Font Preloading Strategy
  4. Self-Hosting Google Fonts
  5. Variable Fonts: One File, Every Weight
  6. size-adjust: Eliminating CLS Completely
  7. System Font Stacks for Body Text
  8. Platform-Specific Font Optimization
  9. Frequently Asked Questions
  10. Conclusion

How Web Fonts Hurt Performance

When a browser encounters a @font-face rule in CSS, it starts a cascade of events:

  1. Download the CSS file containing @font-face declarations
  2. Parse the CSS to find which font files are needed
  3. Initiate font file downloads (typically 20–150KB per family/weight)
  4. Render the page — but what happens to text that needs the font?

This is where FOIT and FOUT come in:

FOIT (Flash of Invisible Text): the browser hides text until the font loads. Users see blank space where text should be. This is the browser’s default behavior for most engines. From a user perspective, the page feels broken. From an LCP perspective, text-based LCP elements won’t be painted until the font loads.

FOUT (Flash of Unstyled Text): text renders immediately in a system fallback font, then swaps to the web font when it loads. Users see a visual “jump” as text reflows. This is better than invisible text but creates CLS if the fallback and web font have different dimensions (different x-height, different character width ratios).

The numbers: Google Fonts’ Inter with two weights (400, 700) adds 72KB of font files over two requests. On a median mobile connection (estimated 8Mbps), this takes 72ms download time. But the real cost is the cascade — the CSS must download first, then the font file download starts. Total latency from request start to font painted: often 300–600ms on mobile.


The Five font-display Values

font-display is a CSS property inside @font-face declarations that controls how the browser handles the font loading period. It’s the single most important font optimization setting.

font-display Strategy Comparison

Click each strategy to see its behavior, trade-offs, and best use case.






Font Preloading Strategy

font-display: swap prevents invisible text, but the font still loads after CSS. Preloading starts the font download earlier — before the browser discovers the @font-face rule in your CSS.

Add this to your HTML <head>:

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

Critical details:
– Always include crossorigin attribute, even for self-hosted fonts. Fonts use CORS and the crossorigin attribute is required for the browser to match the preload to the actual font request.
– Only preload fonts you use above the fold. Preloading 5 font files means 5 parallel downloads competing with your LCP image.
– Only preload woff2 files — modern browsers support woff2 universally. Preloading woff for IE11 compatibility is unnecessary for most sites in 2026.

What not to do: <link rel="preload"> for Google Fonts URLs. Fonts loaded from fonts.googleapis.com require a redirect to fonts.gstatic.com with a browser-specific URL — you can’t predict the exact URL to preload. Self-host instead.


Self-Hosting Google Fonts

Loading Google Fonts from fonts.googleapis.com adds:
1. A DNS lookup for fonts.googleapis.com
2. A DNS lookup for fonts.gstatic.com
3. Two TCP connection establishments
4. Two HTTP requests

These add 100–300ms on first visit. Self-hosting eliminates the external DNS and connection overhead.

Steps to self-host Google Fonts:
1. Go to google-webfonts-helper.herokuapp.com — paste your Google Fonts URL and get pre-configured CSS + font files for download.
2. Upload font files to your server (e.g., /fonts/ directory)
3. Replace your <link rel="stylesheet" href="https://fonts.googleapis.com/css2?..."> with a self-hosted @font-face CSS block
4. Add <link rel="preload"> for your woff2 files

Real impact measurement: one SaaS landing page switched from Google Fonts CDN to self-hosted Inter. Google’s PageSpeed waterfall showed a 280ms reduction in total blocking time. LCP dropped from 2.1s to 1.7s. Both changes were entirely from eliminating the external font requests.


Variable Fonts: One File, Every Weight

A variable font stores the entire weight range (100 through 900) in a single file using OpenType font variations. Instead of loading separate files for regular (400), semibold (600), and bold (700), you load one file and specify the weight with font-weight.

Before variable fonts (3 files):

@font-face { font-family: 'Inter'; font-weight: 400; src: url('inter-regular.woff2'); }
@font-face { font-family: 'Inter'; font-weight: 600; src: url('inter-semibold.woff2'); }
@font-face { font-family: 'Inter'; font-weight: 700; src: url('inter-bold.woff2'); }

With variable font (1 file):

@font-face {
  font-family: 'Inter';
  font-weight: 100 900;
  src: url('inter-var.woff2') format('woff2');
  font-display: swap;
}

The variable font file is larger than a single weight file (e.g., 320KB vs 80KB) but much smaller than 3 separate weight files (240KB combined). If you use 3 or more weights, variable fonts save network requests and reduce total bytes.

Variable font browser support: 97%+ in 2026. Use @supports (font-variation-settings: normal) with a fallback if you need to support older browsers.


size-adjust: Eliminating CLS Completely

Even with font-display: swap, text reflowes when the web font loads if the fallback font has different character widths. size-adjust lets you scale the fallback font to match the web font’s metrics, eliminating the shift.

@font-face {
  font-family: 'Inter-Fallback';
  size-adjust: 107%;         /* Scale up to match Inter's character width */
  ascent-override: 90%;      /* Match Inter's ascender height */
  descent-override: 22%;     /* Match Inter's descender height */
  font-family: Arial;        /* The actual fallback */
}

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

When the web font is loading, browsers use Inter-Fallback (which is Arial scaled to match Inter’s dimensions). When Inter loads and swaps in, the text dimensions are already identical — no layout shift.

Finding the right size-adjust value requires some measurement. The tool at screenspan.net/fallback-font-tool/ calculates the correct values for popular fonts automatically.

Font CLS Impact Simulator

See how font configuration choices affect your Cumulative Layout Shift score.





System Font Stacks for Body Text

If you want perfect font-related CWV scores, use system fonts for body text. System fonts are pre-installed on every device — zero HTTP requests, zero FOIT, zero CLS.

2026 system font stack:

body {
  font-family: 
    system-ui,           /* Modern browsers — picks system's UI font */
    -apple-system,       /* Safari/macOS: San Francisco */
    BlinkMacSystemFont,  /* Chrome on macOS */
    'Segoe UI',          /* Windows */
    Roboto,              /* Android */
    'Noto Sans',         /* Linux / Google's fallback */
    Ubuntu,
    Cantarell,
    sans-serif;
}

This renders as San Francisco on Apple devices, Segoe UI on Windows, Roboto on Android. The result looks professional, native, and loads instantly.

Many high-performing sites use web fonts only for headings (H1–H3) and system fonts for body text. This limits font requests to the minimum needed for brand differentiation while eliminating font-related performance overhead for the content users actually read.


Platform-Specific Font Optimization

WordPress: Install “OMGF | Host Google Fonts Locally” plugin for automatic self-hosting. It downloads and serves Google Fonts from your server and removes the external stylesheet links. Pair with WP Rocket or LiteSpeed Cache to add preload hints automatically.

Shopify: Themes ship with font files hosted on Shopify’s CDN. Use theme settings to reduce font variants — most themes let you select “System” fonts in the customizer, which switches to the system stack instantly. For custom fonts, upload woff2 files to the theme assets folder and update your CSS.

Next.js: Use the built-in next/font module (available since Next.js 13). It automatically self-hosts Google Fonts, generates @font-face CSS, and adds font-display: swap — zero configuration needed:

import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });

Frequently Asked Questions

Does Google Fonts slow down my site?
Yes, measurably. Google Fonts adds 2 external DNS lookups and 2–4 HTTP requests. Self-hosting eliminates these. On first visit from a cold cache, self-hosted fonts are consistently 100–300ms faster.

How many web fonts can I load without hurting performance?
One font family with two weights (regular and bold) is the practical maximum for performance-first sites. Two families (e.g., one for headings, one for body) with two weights each is the absolute limit — four font requests total. Beyond this, consider variable fonts to consolidate.

Should I use WOFF or WOFF2?
Always woff2. It uses Brotli compression and is 30% smaller than woff. Browser support is 97%+ in 2026. Only include woff as a fallback if you have evidence of significant traffic from browsers that don’t support woff2 (IE11, which has essentially zero market share).

Does Google cache my fonts for users who’ve visited other Google Fonts sites?
Google deprecated the shared font cache in 2020 for privacy reasons. Browsers no longer share font caches across origins. The “cached Google Fonts” argument for using their CDN no longer applies — self-hosting is unambiguously better.

How do I find the size-adjust value for my font?
Calculate it by comparing the width of a sample string in both fonts at the same font-size. If your web font renders “The quick brown fox” at 340px wide and Arial renders it at 318px, your size-adjust is approximately 340/318 = 107%.


Conclusion

Font optimization has one of the best effort-to-impact ratios in technical SEO. Adding font-display: swap to your @font-face is a 5-minute change that can reduce your FOIT from 3 seconds to near-zero. Adding a <link rel="preload"> for your critical font starts downloading it before the browser even parses your CSS.

For sites serious about CWV, the stack is: self-host your fonts → preload critical woff2 files → font-display: swap or optionalsize-adjust fallback for zero CLS. For body text where brand differentiation matters less than performance, switch to the system font stack entirely.

Your fonts are configuration. Stop letting them be a liability.


Let Ignited Nepal Handle This

→ Request a Free Technical SEO Audit


Written by the Ignited Nepal SEO team. We build organic search systems for businesses across Nepal, Australia, UAE, USA, UK, and beyond. ignitednepal.com

NR

Article by

Niraj Raut

Head of Search at Ignited Nepal. Drove 340% organic traffic growth for EzyDog (Australia), 4× revenue for The Turf Man (Australia), and 120% month-on-month traffic growth for ThemeGrill (Nepal). Keynote speaker at WordCamp Nepal 2023 and verified WordPress.org open-source contributor.