Preload vs Prefetch vs Preconnect for Fonts
This guide is part of the Font Preloading & Resource Hints section, under the wider Font Loading & Delivery Strategies blueprint. The four hints — preload, prefetch, preconnect, dns-prefetch — look interchangeable and are constantly misused. Applied to the wrong font at the wrong priority they either do nothing or actively delay your LCP. This page maps each hint to the exact font scenario it was built for, with a decision matrix and the two mistakes that cause a double-fetch.
Problem Statement
<link rel="preload"> fetches a resource the current page will definitely need, at high priority, before the parser discovers it. <link rel="prefetch"> fetches a resource a future navigation will probably need, at the lowest priority, when the browser is idle. <link rel="preconnect"> opens the TCP + TLS connection to an origin early but downloads nothing. <link rel="dns-prefetch"> resolves only the DNS for an origin — a cheap fallback for preconnect. Engineers reach for preload reflexively, preloading every weight and every third-party font, which saturates bandwidth and pushes the real LCP resource back. The fix is matching the hint to the font's role: critical-now, next-page, or cross-origin.
Prerequisites
- Fonts served as WOFF2 with long-lived immutable
Cache-Controlso prefetched and preloaded assets survive into the navigation that uses them. - For self-hosted fonts, you know which single weight paints the above-the-fold LCP text.
- For third-party fonts (Google Fonts, Adobe Fonts), you have the connecting origin(s) — e.g.
fonts.googleapis.com(CSS) andfonts.gstatic.com(font files). - Every
@font-faceusesfont-display: swapso a missed hint degrades gracefully rather than hiding text.
Implementation: The Correct Hints
Put the critical-path hints first in <head>, before the stylesheet that references the fonts, so the preload scanner acts before CSSOM construction.
Correct font resource hints in <head>
<head>
<!-- Self-hosted critical weight: fetch now, high priority -->
<link rel="preload" href="/fonts/inter-regular.woff2"
as="font" type="font/woff2" crossorigin>
<!-- Third-party font origin: open the connection early, download nothing -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<!-- Stylesheet that declares @font-face comes after the hints -->
<link rel="stylesheet" href="/css/main.css">
</head>
Three load-bearing details. First, as="font" is mandatory — without it the browser cannot set the right priority or apply the Accept headers, and may warn that the preload went unused. Second, crossorigin is required even for same-origin fonts: font fetches always use anonymous CORS mode, so a preload without crossorigin lands in a different cache partition than the @font-face request and the browser fetches the file twice. Third, preconnect to a font file origin (fonts.gstatic.com) also needs crossorigin, because the font fetch it warms up is itself anonymous-CORS; a preconnect without it opens a connection that the real request cannot reuse. The paired dns-prefetch is a cheap fallback for browsers that ignore or cap preconnect.
For the next-navigation case — say you know the checkout page uses a display weight the current page does not — use prefetch, which fires at idle priority and will not compete with the current page's LCP.
Anti-pattern fixes
<!-- WRONG: preload missing crossorigin -> double fetch -->
<link rel="preload" href="/fonts/inter-regular.woff2" as="font" type="font/woff2">
<!-- FIX: add crossorigin -->
<link rel="preload" href="/fonts/inter-regular.woff2" as="font" type="font/woff2" crossorigin>
<!-- WRONG: preloading a next-page-only font on this page -->
<link rel="preload" href="/fonts/display-black.woff2" as="font" type="font/woff2" crossorigin>
<!-- FIX: it's for the *next* navigation -> prefetch (idle priority) -->
<link rel="prefetch" href="/fonts/display-black.woff2" as="font" crossorigin>
<!-- WRONG: preconnect to a third-party font origin without crossorigin -->
<link rel="preconnect" href="https://fonts.gstatic.com">
<!-- FIX: font fetches are anonymous-CORS, so the warmed connection must match -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
The double-fetch trap is the single most common font-hint bug. A preloaded font that is fetched again by the CSS engine doubles your transfer and wastes the high-priority slot. Verify there is exactly one network row per font URL. Preload only above-the-fold weights — one or two files — and let the rest load on discovery. For deeper LCP-safe preloading, see preloading critical fonts without blocking LCP.
Decision Matrix
| Hint | What it does | Priority | Use for fonts when… | crossorigin? |
|---|---|---|---|---|
preload |
Fetches the file now, before discovery | High | The weight paints above-the-fold text on this page | Required |
preconnect |
Opens TCP + TLS, no download | n/a | The font lives on a third-party origin (fonts.gstatic.com) |
Required |
dns-prefetch |
Resolves DNS only | n/a | Fallback for preconnect, or many cross-origins |
No |
prefetch |
Fetches at idle, for a future page | Lowest | A weight only the next navigation needs | Recommended |
Use at most one or two preload hints; pair every third-party preconnect with a dns-prefetch fallback; reserve prefetch for assets the current page does not render.
Verification
Confirm each hint behaves as intended in the Network panel.
- DevTools → Network → Filter: Font. Reload with cache disabled.
- Read the Priority column. A
preloaded font should show High; aprefetched font should show Lowest. If your "preload" shows Low, theas="font"attribute is missing. - Check the Initiator column: the preloaded font's initiator should be the
<link>element, notstylesheet. Astylesheetinitiator means the preload was ignored or mismatched. - Confirm exactly one network row per font URL. Two rows for the same URL is the double-fetch signature — almost always a missing
crossorigin. - For
preconnect, open the font request's Timing tab; the "Initial connection" and "SSL" segments should be near-zero because the connection was already warm. If they are not, the preconnect lackedcrossoriginand the warmed socket went unused.
Common Pitfalls
- Omitting
crossoriginon a fontpreload. Font requests are anonymous-CORS; the preloaded response lands in a different cache partition and the CSS engine fetches the file again. Always addcrossorigin, even same-origin. preconnectwithoutcrossoriginto a font-file origin. The warmed connection does not match the anonymous-CORS font fetch, so the browser opens a second connection and the hint is wasted.- Using
preloadfor a next-page font. It steals a high-priority slot from the current LCP. Useprefetch, which runs at idle and waits for the navigation. - Preloading too many weights. Three or more font preloads saturate the connection and delay the real LCP candidate. Limit to the one or two above-the-fold weights.
- Missing
as="font". Without it the browser cannot prioritize correctly, may apply the wrong Accept header, and often warns the preload was unused.
FAQ
Why does a font I preloaded get fetched twice?
The preload <link> lacks crossorigin. Fonts are always requested in anonymous-CORS mode, so a preload without crossorigin is cached separately from the @font-face request; the browser cannot reuse it and fetches the file a second time. Add crossorigin to the preload — and to any preconnect aimed at a font-file origin.
Should I preconnect or preload third-party fonts like Google Fonts?
Preconnect to the origins (fonts.googleapis.com for the CSS and fonts.gstatic.com for the files, both with crossorigin), because you usually do not know the exact hashed font URL ahead of time. Preconnect warms the connection so the font request skips DNS, TCP, and TLS once the CSS reveals the URL. Preload only when you control and know the final font URL.
When is prefetch actually worth it for fonts?
Only when a future navigation needs a weight the current page does not render — for example a marketing display face used solely on the next page in a known funnel. It runs at the lowest priority during idle time, so it never competes with the current page's LCP, and the cached file makes the next navigation paint instantly.