Custom web fonts are a dual CLS and FCP risk: without font-display: swap, the browser hides text entirely until the font loads (FOIT — Flash of Invisible Text), delaying FCP and making the page appear blank. When the font finally loads and text reflows to a different line-height or character width, it produces layout shift that contributes to CLS. Google Fonts loaded via external <link> tags compound this by adding an extra DNS lookup and cross-origin connection before the font file starts downloading. ISO-25010 time-behaviour covers both of these: rendering delay and post-render layout instability. Marketing pages with two or more font families at multiple weights can accumulate 300–600ms of font-related latency on slow connections.
Medium because font loading strategy affects both FCP (invisible text delay) and CLS (text reflow on font swap), but the impact is bounded to pages using custom web fonts and is fully remediable.
Replace Google Fonts <link> tags with next/font — it self-hosts fonts, applies font-display: swap automatically, and generates preload hints so the font starts downloading immediately.
// app/layout.tsx — next/font handles everything
import { Inter, Playfair_Display } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
})
const playfair = Playfair_Display({
subsets: ['latin'],
weight: ['400', '700'], // only load weights you actually use
display: 'swap',
})
export default function RootLayout({ children }) {
return <html className={`${inter.className}`}>{children}</html>
}
For manual setups without next/font, add font-display: swap to every @font-face in src/styles/globals.css and a <link rel="preload" as="font" crossorigin> for the primary font file in the <head>.
ID: marketing-page-speed.resource-optimization.font-loading-strategy
Severity: medium
What to look for: Custom font loading is a significant source of both FCP delay and CLS. Check: (1) @font-face declarations in CSS for font-display setting — auto (default) acts like block and causes invisible text (FOIT), (2) Google Fonts loaded via <link> vs. next/font (which self-hosts), (3) font file sizes — WOFF2 > 100KB per family is large, (4) number of font families and weights loaded — loading 4 families at 4 weights each is 16 network requests, (5) <link rel="preload"> for critical font files. Before evaluating, extract and quote the @font-face declarations and font-display values from the CSS to verify the loading strategy. Count all instances found and enumerate each.
Pass criteria: Custom fonts use font-display: swap or optional. Google Fonts are loaded via next/font or equivalent self-hosting mechanism. Critical font files are preloaded. Total font weight < 100KB WOFF2. At least 1 implementation must be confirmed.
Fail criteria: Fonts use font-display: auto or block. Google Fonts are loaded as external <link> without cross-origin resource hints. Multiple font families with many weights are loaded without subsetting.
Skip (N/A) when: Project uses only system fonts (no custom fonts detected).
Cross-reference: The cls-threshold check in Core Web Vitals measures the layout shift impact of the font loading strategy verified here.
Detail on fail: "globals.css imports Inter and Playfair Display from Google Fonts via <link> with no font-display. Both block text render — FOIT delay is visible on slow connections."
Remediation: Use next/font for automatic optimization:
// next/font handles font-display:swap, preloading, and self-hosting automatically
import { Inter, Playfair_Display } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
preload: true,
})
const playfair = Playfair_Display({
subsets: ['latin'],
weight: ['400', '700'], // only load what you use
display: 'swap',
})
For manual setups: font-display: swap in every @font-face, and <link rel="preload" as="font" crossorigin> for the primary font file.