· Performance · 6 min read
How to Preload Web Fonts (and When You Should Not)
The exact preload tag for a web font, why it needs crossorigin, and the cases where preloading slows your page down instead of speeding it up.
Preloading a font tells the browser to start downloading it before it would normally find out it needs it. Done for the right file, it can make your text appear in the right font sooner. Done for the wrong files, it wastes bandwidth that the rest of the page needed.
This guide covers the markup, the attribute that trips most people up, and how to tell whether a preload is helping.
Why fonts are discovered late
A browser cannot request a font just because a stylesheet mentions it. The usual chain looks like this:
- The HTML loads and the browser finds your stylesheet.
- The stylesheet downloads and is parsed, including the
@font-facerules. - The browser works out which elements are on screen and which font each one uses.
- Only now does it request the font files that visible text actually needs.
By step 4, the page may already be ready to paint. web.dev’s Preload critical assets describes this as a late-discovered resource. A preload moves the font request up to step 1.
The preload tag
Put this in the <head>, ideally before your stylesheets:
<link rel="preload" href="/fonts/brand-text-regular.woff2" as="font" type="font/woff2" crossorigin />Each attribute has a job. MDN’s rel=preload reference documents all of them.
hrefmust resolve to exactly the same URL as thesrcin your@font-facerule. A different path, host, or query string means the browser treats them as two different files.as="font"tells the browser what it is fetching, so it gets the right priority and can be matched with the later font request.type="font/woff2"lets a browser that does not support the format skip the download entirely.crossoriginis required, even when the font is on your own domain.
Why crossorigin is required
Browsers always fetch fonts in anonymous CORS mode. A preload without crossorigin is made in a different mode, so when the stylesheet later asks for the font, the browser cannot reuse the preloaded copy. It downloads the file a second time. MDN notes that the attribute must match the resource’s CORS mode even when the fetch is not cross-origin.
If you only remember one thing from this guide: a font preload without crossorigin downloads the font twice.
Preloading from a server header
If you cannot edit the HTML, the same hint can be sent as an HTTP header:
Link: </fonts/brand-text-regular.woff2>; rel=preload; as=font; type="font/woff2"; crossoriginWhen preloading helps
- The font is used in the first screen. Typically your main body text or the main heading.
- The
@font-facerules are in an external stylesheet, especially one loaded by another stylesheet through@import. Every extra step in that chain delays discovery. - You use
font-display: optional. Since Chrome 83, Chrome waits briefly for a preloaded optional font before the first render, which makes it much more likely the font is used without any layout shift. See web.dev’s article on preloading optional fonts and Choosing a font-display value.
In practice that usually means preloading one or two files: the regular weight of your text font, and maybe the heading weight.
When not to preload
Fonts that are not in the first screen. A preload is always fetched, whether or not the page ends up using the font. Italic, a footer font, or a font only used in a dialog are better left to load normally. Chrome warns in the console when a preloaded resource is not used within a few seconds of the page loading.
Every weight and style. Preloads compete with your CSS, scripts, and the main image for bandwidth early in the page load. web.dev’s Best practices for fonts points out that preloading takes browser resources away from other resources. Five preloaded fonts can easily delay the content they were meant to speed up.
Font subsets split with unicode-range. A preload ignores unicode-range, so it downloads the file even if the page contains none of the characters in that range. Preload the subset you know the first screen needs, or nothing.
More than one format. Preload only the WOFF2 file. Preloading a WOFF fallback as well downloads both.
Fonts hosted by a third-party service. Services such as Google Fonts generate the font URLs for you, and those URLs can change. A preload with an outdated URL fetches a file nobody uses. Connect to the font host early instead:
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />When your framework already does it. Some frameworks and font tools add preload tags for you; for example, Next.js’s next/font and Astro’s Fonts API can generate them. Check the page source before adding your own, or the font may be requested twice.
An alternative: inline the @font-face rules
The web.dev font guide also suggests a fix at the root of the problem: put the @font-face rules in a <style> block in the <head>, so the browser does not wait for an external stylesheet to learn about them.
<head>
<style>
@font-face {
font-family: 'Brand Text';
src: url('/fonts/brand-text-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
</style>
<link rel="stylesheet" href="/css/site.css" />
</head>This removes one step from the chain. The browser still waits until it knows the font is needed for visible text, so a preload can still help for the most important file.
A complete example
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Only the font used by the first screen of text -->
<link rel="preload" href="/fonts/brand-text-regular.woff2" as="font" type="font/woff2" crossorigin />
<link rel="stylesheet" href="/css/fonts.css" />
<link rel="stylesheet" href="/css/site.css" />
</head>/* /css/fonts.css */
@font-face {
font-family: 'Brand Text';
src: url('/fonts/brand-text-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Brand Text';
src: url('/fonts/brand-text-italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}The regular weight is preloaded because almost every page uses it right away. The italic file is not, because it may not be needed at all.
Check that the preload works
- One request per font. Open the Network panel, filter by “Font”, and reload with the cache disabled. Each font should appear once. Two requests for the same file usually mean a missing
crossoriginor a URL that does not match the@font-facerule exactly. - It starts early. In the waterfall, the preloaded font should start at about the same time as your stylesheets, not after them.
- No console warning. A “preloaded but not used” warning means the page did not need that font early, or the URLs do not match.
- Measure. Compare before and after with Lighthouse or another lab tool, on a throttled connection. Keep the preload only if the font shows up sooner and nothing else got slower.
Preloading a FontPivot kit
The web font kit from the font converter or the font viewer puts the WOFF2 file in a fonts/ folder and writes an @font-face rule with a relative url(). Once you have placed the files on your site, use the same final URL in the preload’s href. Browsers compare the resolved URLs, so /fonts/brand-text-regular.woff2 in the preload and ./fonts/brand-text-regular.woff2 in a stylesheet at the site root count as a match.
Starting from a desktop font? Convert it first with TTF to WOFF2 or OTF to WOFF2, then check the result in the font viewer.
Summary
- Preload only the one or two fonts the first screen needs.
- Always include
as="font",type="font/woff2", andcrossorigin. - Make the
hrefmatch the@font-faceURL exactly. - Do not preload every weight,
unicode-rangesubsets, WOFF fallbacks, or third-party font URLs. - Check the Network panel for duplicate requests and the console for unused-preload warnings.
Further reading
- MDN:
rel=preloadandcrossorigin - web.dev: Preload critical assets to improve loading speed
- Can I use:
rel=preload