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-Control so 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) and fonts.gstatic.com (font files).
  • Every @font-face uses font-display: swap so 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

Which font resource hint to useA decision flow mapping font scenarios to preload, prefetch, preconnect, or dns-prefetch.Font on aknown origin?Self-host, criticalthis pageThird-partyoriginNext navigationonlypreload + crossoriginpreconnect+ dns-prefetchprefetch (idle)
Match the font's role — critical-now, cross-origin, or next-page — to the hint built for it.
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.

  1. DevTools → Network → Filter: Font. Reload with cache disabled.
  2. Read the Priority column. A preloaded font should show High; a prefetched font should show Lowest. If your "preload" shows Low, the as="font" attribute is missing.
  3. Check the Initiator column: the preloaded font's initiator should be the <link> element, not stylesheet. A stylesheet initiator means the preload was ignored or mismatched.
  4. Confirm exactly one network row per font URL. Two rows for the same URL is the double-fetch signature — almost always a missing crossorigin.
  5. 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 lacked crossorigin and the warmed socket went unused.

Common Pitfalls

  • Omitting crossorigin on a font preload. Font requests are anonymous-CORS; the preloaded response lands in a different cache partition and the CSS engine fetches the file again. Always add crossorigin, even same-origin.
  • preconnect without crossorigin to 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 preload for a next-page font. It steals a high-priority slot from the current LCP. Use prefetch, 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.

Related