Font Optimization for Your Own Store: System Fonts, font-display, Preloading, and FOIT
1. Why fonts block text from showing
Until the browser has the font file, it doesn’t know what the text should look like, and its default behavior is to hide the text (FOIT, Flash of Invisible Text). In older browsers that hidden period was 3 seconds; newer browsers cut it to 1 second and then force a fallback font. The problem: the body text on your first screen is the LCP element, so if it’s waiting on a font, LCP is held back by the font download.
A typical mistake on stores you run yourself: to add “design polish,” the theme author loads every weight of Inter or Roboto from Google Fonts (100 through 900, 9 weights), each 100KB, so one load pulls 900KB. On 3G the fonts alone take 3 to 5 seconds to download, and users see a blank screen. For the font-related Core Web Vitals metrics, see Core Web Vitals Optimization, and for how to set up your CDN, see CDN global acceleration.
| Issue | Performance impact | Typical size |
|---|---|---|
| Loading all weights (9) | 800KB extra on the first screen | 80 to 120KB per weight |
| Unsubsetted Chinese font | 2 to 8MB per file | Far over the first-screen budget |
| font-display defaults to block | FOIT hides text for 1 to 3 seconds | Text is invisible |
| Key fonts not preloaded | Font discovered late, 200ms delay | LCP is slowed down |
| Using many families (more than 3) | Every family has to download | 300 to 500KB total |
2. The system font stack: the default answer for B2B sites
A system font stack uses fonts already installed on the user’s device, so there are zero network requests and text shows immediately. B2B stores you run yourself are mostly spec tables, case descriptions, and technical articles, so the typography doesn’t need decorative or custom brand fonts — a system font stack looks perfectly good and stays consistent across platforms.
A CSS stack you can drop in as-is: font-family: -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, “Helvetica Neue”, Arial, “PingFang SC”, “Microsoft YaHei”, sans-serif. It covers macOS, Windows, Android and iOS, with a system font for both Chinese and English. Counterintuitive take:Using system fonts won’t make your site look “unprofessional”. In fact, design benchmarks like GitHub, Stripe and Linear all use system font stacks. For how to pick a WP theme, see WordPress tech stack.
| Solution | Above-the-fold text | Extra downloads | Best for |
|---|---|---|---|
| System fonts only | Shows instantly | 0KB | Most B2B standalone sites |
| 1 web font for headings | Swapped in after fallback | 30 to 80KB | Sites that want a strong brand feel |
| 2 web font families | Swapped in after fallback | 80 to 150KB | Design-led brand sites |
| 3+ web fonts | Noticeable FOUT/FOIT | >200KB | Not recommended |
| Full Chinese web font | FOIT for several seconds | 2 to 8MB | Barely viable |
3. font-display: swap and FOIT: don’t hide your text
font-display is an @font-face property that controls how text renders while the font loads. Four values: auto (browser default, used to be block), block (hidden briefly, then falls back after 3 seconds), swap (show the fallback immediately, swap in the font once it loads), and optional (the browser decides, and tends not to download the font at all).
B2B standalone sites should always use swap. On a 3G connection, block means a blank above-the-fold area for 3 seconds, and visitors are long gone by then. optional suits fonts that aren’t critical to the brand, but it’s unpredictable. A common misconception is that “swap causes font flicker (FOUT)”, but for a B2B site, letting users read your specs and contact details matters far more than waiting on a fancy heading. A font flickering is perfectly acceptable; text nobody can see is the real loss. For CSS/JS loading strategy, see CSS/JS optimization.
| font-display | Blocking period | After the font loads | Use cases |
|---|---|---|---|
| auto/block | Text invisible for up to 3 seconds | Swapped to the web font | Not recommended |
| swap | Fallback shows immediately | Swapped once the font loads | Default for B2B sites |
| fallback | Hidden for 100ms, then fallback | Swapped only if loaded within 3 seconds | Semi-critical fonts |
| optional | Hidden for 100ms, then fallback | Not swapped on this visit | Purely decorative fonts |
4. preload and subsetting: make the fonts that should be fast, fast
If your above-the-fold heading has to use a web font, use preload to tell the browser to download it early at high priority: add <link rel=”preload” href=”font.woff2″ as=”font” type=”font/woff2″ crossorigin> in the head. The font then starts downloading before the CSS is parsed, which can pull LCP forward by 200 to 400 milliseconds. Note that you can’t omit the crossorigin attribute, or preload won’t work.
Subsetting is the other optimization you have to do. For English fonts, keep only Latin letters, digits, and common symbols, which cuts the file from 100KB to 30KB. Chinese fonts have such large character sets that even after subsetting they run 200KB to 1MB, which is why Chinese-language sites are better off using system fonts. Use glyphhanger or the text parameter in Google Fonts to load only the characters you actually use. One real example: an above-the-fold headline uses just 20 Chinese characters, so you subset only those 20, and the file drops from 4MB to 5KB. For CDN caching, see CDN comparison.
| Font optimization action | Size / latency gain | Implementation difficulty | Recommendation |
|---|---|---|---|
| Use woff2 only | 30% smaller than ttf | Low | Must do |
| Load only weights 400 and 700 | 60% less download | Low | Must do |
| Subset the English character set | Another 50% off | Medium | Recommended |
| Preload above-the-fold fonts | LCP 200 to 400ms earlier | Low | Above-the-fold headline uses |
| Full Chinese web font | Negative | High | Don’t do it |
5. Validation: check the font loading waterfall in DevTools
How do you validate your font optimization? Open the Network panel in Chrome DevTools, filter by Font, and check three things: the number of font files (should be no more than 2 families, no more than 2 weights per family), the size of each font file (under 50KB is healthy), and when each font starts downloading (with preload it should be among the earliest requests, not discovered after CSS parsing). If any font file is over 100KB, it’s worth going back to check whether the weights or subsets are misconfigured.
Then use Lighthouse to check the “Avoid layout shifts from web fonts” and “Preload key requests” audits. An actionable checklist: font-display is swap everywhere, above-the-fold fonts are preloaded, woff2 format, no legacy ttf/eot formats, and Google Fonts loaded with the display=swap parameter. For how to handle fonts on multilingual sites, see Charts and tables on multilingual sites. For site monitoring, see Site monitoring alerts.
| Validation item | Pass criteria | Measurement tool |
|---|---|---|
| font-display | All swap or fallback | CSS review |
| Above-the-fold fonts preloaded | Above-the-fold headline font is preloaded | DevTools Network |
| Font file format | woff2 only | Filter Network by Font |
| Single font file size | ≤50KB | Network Size column |
| Number of font families | ≤2 | CSS review |
| LCP 75th percentile | ≤2.5s | CrUX / RUM |
FAQ
Do B2B sites really need web fonts?
What’s the difference between font-display:swap and block?
Can I use a web font for Chinese text?
Why does preloading a font need crossorigin?
What’s the fastest way to load Google Fonts?
How do I verify font optimization is done?
6. FOUT and layout shift: how to stop the page from jumping when fonts swap
With the swap strategy, when the fallback font is replaced by the web font, the different character widths and heights can make the page jump. That’s where CLS (Cumulative Layout Shift) comes from. Stripe measured it: a single font swap causes 0.05 to 0.1 CLS on average, right on the edge of the 0.1 passing threshold. On B2B sites, long body copy and many paragraphs mean the line-break differences from the swap make images jump up and down.
The fix is the font-size-adjust and size-adjust descriptors. In @font-face, use size-adjust: 105% and ascent-override to bring the web font’s metrics close to the fallback’s, so the swap barely shifts anything. An easier route is to pick a fallback whose metrics are close to the web font’s, for example Arial as the fallback for Inter, or just use a system font stack and skip the swap entirely. One concrete check: run Lighthouse’s “Avoid layout shifts from web fonts” audit. If the score is low, first see whether swap is the cause, then decide whether to add size-adjust.
| Handling FOUT | Fix CLS | Implementation difficulty |
|---|---|---|
| Leave it, accept the flicker | 0.05 to 0.1 | None |
| Fine-tune metrics with size-adjust | Get it below 0.02 | Medium, needs testing |
| Pick a fallback with close metrics | Get it below 0.03 | Low |
| Just drop web fonts | 0 | Zero cost |
Final thoughts
The core call on fonts is simple: people come to a B2B site to read specs, see case studies, and find a contact, not to admire typography. A system font stack is the right answer 95% of the time, and the few hundred KB and few hundred milliseconds you save matter far more than the visual difference. Even if you keep web fonts, hold to four rules: two families, two weights, woff2 format, swap loading.
Three steps to ship: first, switch your theme fonts to a system font stack this week and delete the extra Google Fonts references. Second, add font-display:swap to every web font you keep, cut to just 400 and 700, and self-host the woff2 files. Third, preload the above-the-fold heading font and add crossorigin. Check in two weeks whether LCP and your font-related audits have improved.
One line worth remembering: nobody stays a second longer because your site uses Inter, but they will close the tab if they wait 3 seconds for a font to load. Fonts are a detail, but details decide whether you win those first 2.5 seconds.
