From harness-claude
Optimizes web font loading using font-display for FOIT/FOUT control, preloading, Unicode subsetting, variable fonts, WOFF2 compression, and self-hosting vs CDN to reduce CLS and improve LCP.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Master web font loading optimization — font-display strategies for FOIT/FOUT control, Unicode range subsetting, variable fonts for multi-weight reduction, WOFF2 compression, preloading critical fonts, self-hosting versus CDN delivery, and the Font Loading API for programmatic control.
Optimizes web font loading strategies: font-display to avoid FOUT/FOIT, variable fonts, subsetting, system stacks, self-hosting vs CDNs. Reduces LCP/CLS.
Provides page speed optimization guidelines and HTML/CSS/JS patterns to meet Core Web Vitals targets (LCP<2.5s, FID<100ms, CLS<0.1) for funnel pages.
Guides web typography: font selection, pairing, scaling, spacing, responsive type, variable fonts, loading optimization, accessibility, and readability.
Share bugs, ideas, or general feedback.
Master web font loading optimization — font-display strategies for FOIT/FOUT control, Unicode range subsetting, variable fonts for multi-weight reduction, WOFF2 compression, preloading critical fonts, self-hosting versus CDN delivery, and the Font Loading API for programmatic control.
Set font-display for every @font-face rule. This controls browser behavior while the font downloads:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-weight: 100 900;
font-display: swap; /* show fallback immediately, swap when loaded */
}
font-display value | Behavior
-------------------|--------------------------------------------------
swap | Show fallback immediately, swap to web font when ready
optional | Show fallback; use web font only if it loads very quickly (~100ms)
fallback | Brief invisible period (~100ms), then fallback, then swap
block | Invisible text for up to 3s, then fallback (DEFAULT — avoid this)
auto | Browser decides (usually same as block)
Use swap for body text where readability matters most. Use optional for non-critical decorative fonts where layout stability matters more than exact typography.
Preload critical fonts. The browser does not discover font files until it parses CSS. Preloading eliminates this delay:
<head>
<!-- Preload the primary font used for body text -->
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin />
<!-- crossorigin is REQUIRED even for same-origin fonts -->
</head>
Only preload 1-2 fonts that are used above the fold. Preloading too many fonts competes with other critical resources.
Subset fonts to reduce file size. Remove unused character ranges to dramatically reduce font size:
# Using pyftsubset (from fonttools)
pip install fonttools brotli
# Latin subset only (covers English and most Western European languages)
pyftsubset Inter-Regular.ttf \
--output-file=inter-latin.woff2 \
--flavor=woff2 \
--layout-features='kern,liga,calt' \
--unicodes='U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD'
/* Declare Unicode ranges for subset loading */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-latin.woff2') format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-cyrillic.woff2') format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
font-display: swap;
}
/* Browser only downloads the subset(s) needed for the page content */
Use variable fonts to eliminate multiple files. A single variable font file replaces 6-12 static weight files:
/* One file covers all weights from 100 to 900 */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
/* Use any weight without additional file downloads */
h1 {
font-weight: 750;
} /* not limited to 700 or 800 */
body {
font-weight: 400;
}
.bold {
font-weight: 650;
}
Comparison for Inter with 4 weights (400, 500, 600, 700):
Static files: 4 x ~25KB = ~100KB
Variable font: 1 x ~50KB = ~50KB (50% smaller)
Minimize layout shift from font swap. Match the fallback font metrics to the web font:
/* Size-adjusted fallback to match Inter metrics */
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107.64%;
ascent-override: 90.49%;
descent-override: 22.56%;
line-gap-override: 0%;
}
body {
font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
}
# Generate metric overrides automatically
npx @capsizecss/metrics Inter
# Or use: https://screenspan.net/fallback
Self-host fonts instead of using Google Fonts CDN. Self-hosting eliminates third-party DNS lookups and connection overhead, enables HTTP cache partition benefits, and gives full control over caching:
# Download Google Fonts for self-hosting
npx google-fonts-helper download -f Inter -s latin -w 400,500,600,700
# Or use: https://gwfh.mranftl.com/fonts (google-webfonts-helper)
Use the Font Loading API for fine-grained control. Programmatically manage font loading when CSS alone is insufficient:
// Load fonts programmatically with timeout
async function loadFonts() {
const font = new FontFace('Inter', 'url(/fonts/inter-var.woff2)', {
weight: '100 900',
display: 'swap',
});
try {
const loaded = await Promise.race([
font.load(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Font timeout')), 3000)),
]);
document.fonts.add(loaded);
document.documentElement.classList.add('fonts-loaded');
} catch {
document.documentElement.classList.add('fonts-failed');
}
}
WOFF2 uses Brotli compression and achieves 30-50% smaller files than WOFF. All modern browsers support WOFF2. There is no reason to serve WOFF, TTF, or EOT to modern browsers. A typical font file: TTF ~150KB, WOFF ~90KB, WOFF2 ~50KB. Always convert to WOFF2 and provide only WOFF2 in @font-face src unless supporting IE11 (which requires WOFF).
GitHub uses a single variable weight Inter font file (WOFF2, ~50KB) self-hosted on their CDN. They preload it with <link rel="preload" as="font" type="font/woff2" crossorigin> and use font-display: swap with a size-adjusted system font fallback. The fallback uses size-adjust and ascent-override to match Inter's metrics within 1%, virtually eliminating visible reflow on font swap. Combined with Latin subsetting, their font loading adds zero render-blocking time and <5 CLS points from the swap.
Stripe's documentation site uses a two-stage font loading strategy. Stage 1 loads the regular weight (used for body text) with preload and font-display: swap. Stage 2 loads bold and italic variants on requestIdleCallback after the page is interactive. The CSS classes fonts-stage-1 and fonts-stage-2 are added to the document element, allowing the design system to progressively enhance typography. This ensures body text renders within 100ms while non-critical weights load in the background without competing for bandwidth.
Preloading too many fonts. Preloading 4+ font files competes with critical CSS, JavaScript, and LCP images for bandwidth. Preload only the 1-2 fonts used in above-the-fold body text.
Using font-display: block on body text. This causes up to 3 seconds of invisible text (FOIT) on slow connections. Use swap for body text so content is immediately readable. Reserve block only for icon fonts where showing the wrong character would be confusing.
Loading all weights when only 2 are used. If the page uses regular (400) and bold (700), loading light (300), medium (500), semi-bold (600), and extra-bold (800) wastes bandwidth. Audit which weights are actually used and load only those.
Not specifying crossorigin on font preload. Font requests are always CORS anonymous requests. Without the crossorigin attribute on <link rel="preload">, the preloaded font and the CSS-requested font are treated as different requests, causing a double download.