Why Fonts Need crossorigin: Preload, CORS and the font-crossorigin Warning

By · Published

Fonts need crossorigin because browsers always fetch web fonts in anonymous-mode CORS, even when the font is on your own domain. A <link rel="preload" as="font"> without the attribute is a different kind of request from the one the CSS makes, so the browser can’t reuse it, and the font downloads twice. Add crossorigin to every font preload, and to the preconnect for any font host.

Why do fonts need crossorigin?

The CSS Fonts specification’s font fetching requirements make every @font-face download a CORS request in anonymous mode. Images and stylesheets don’t work this way; fonts do, which is why this catches people who’ve preloaded other assets without trouble.

MDN’s preload reference states it directly: font and fetch preloading requires the crossorigin attribute, and the attribute “needs to be set to match the resource’s CORS and credentials mode, even when the fetch is not cross-origin.”

A preload is only used if it matches the later request: same URL, same destination (as), same CORS mode. Without crossorigin, the preload is a no-CORS request; the CSS then asks for the font in CORS mode, finds no matching preload, and fetches it again. web.dev’s preload guide puts it in one line: “Fonts preloaded without the crossorigin attribute will be fetched twice.”

So the missing attribute doesn’t just waste the preload. It doubles the font’s bandwidth and still leaves the text waiting.

What’s the correct markup?

<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>

Each attribute has a job:

Attribute Value Why
rel preload Fetch early, before the CSS asks.
as font Sets the request’s destination and priority. Without it the preload won’t match.
type font/woff2 Browsers that can’t use the format skip the download.
crossorigin empty or anonymous Matches the anonymous CORS mode the CSS will use.

A bare crossorigin and crossorigin="anonymous" mean the same thing. Don’t use crossorigin="use-credentials" for fonts; the font request is made without credentials, so a credentialed preload won’t match either.

Preload only the one or two fonts the first screen renders with. Preloading every weight competes with the HTML, CSS and hero image for bandwidth.

What about Google Fonts and other font CDNs?

When fonts come from another origin, two more things apply.

Preconnect with crossorigin. web.dev’s guide to preconnect says fonts are loaded in anonymous mode, so “you must set the crossorigin attribute with the preconnect hint”, and without it “the browser only performs the DNS lookup.” For Google Fonts that means:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

The stylesheet comes from fonts.googleapis.com (no CORS, no attribute); the font files come from fonts.gstatic.com (CORS, attribute required).

The font server must allow your origin. A cross-origin font is only used if the response carries Access-Control-Allow-Origin. Google Fonts sends it. If you host fonts on your own CDN hostname, you have to send it yourself.

nginx:

location ~* \.(?:woff2|woff)$ {
    add_header Access-Control-Allow-Origin "https://www.example.com" always;
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

Apache:

<FilesMatch "\.(woff2|woff)$">
  Header set Access-Control-Allow-Origin "https://www.example.com"
</FilesMatch>

Use * instead of a specific origin if several sites share the font host. If you use a specific origin and serve more than one, send Vary: Origin so caches don’t hand one site’s header to another.

How do you tell if a font is being downloaded twice?

Open DevTools, go to Network, filter by Font, and reload with the cache disabled. If the same .woff2 appears twice, the preload didn’t match. Chrome also logs a Console warning when a preload goes unused, saying the preload was found but not used because the request’s credentials mode doesn’t match, and pointing you at the crossorigin attribute.

From the command line, check the raw HTML for font preloads missing the attribute:

curl -s https://example.com/ | grep -io '<link[^>]*as="font"[^>]*>' | grep -iv crossorigin

Any line this prints is a font preload without crossorigin. To confirm a cross-origin font host sends the CORS header:

curl -s -D - -o /dev/null -H 'Origin: https://www.example.com' \
  https://cdn.example.com/fonts/inter-var.woff2 | grep -i access-control-allow-origin

No output means the browser will refuse the font, and the page falls back to a system font after a wasted download.

What does the font-crossorigin warning mean in bseoa?

bseoa checks every <link rel="preload" as="font"> on every page it crawls and reports the ones without a crossorigin attribute. The warning appears as font crossorigin from the performance analyzer and as font missing crossorigin from the Core Web Vitals analyzer; they’re the same finding seen from two angles, and one fix clears both. The same check also reports font preloads that aren’t WOFF2.

What it doesn’t check: @font-face rules inside your CSS files, and whether a font host sends Access-Control-Allow-Origin. Use the curl commands above for those.

The fix usually lives in one template, the <head> partial, so one change clears the warning on every page. To find every page carrying it, bseoa’s website crawler checks the whole site in one run and lists each page with its warnings.

Why does this matter for SEO?

Indirectly, through speed. A font fetched twice delays text rendered in that font, and when the largest element on the first screen is a heading or paragraph, that’s your Largest Contentful Paint. How to diagnose Core Web Vitals failures at the page level walks through finding which element is the LCP. Font preloads are also a line on the pre-deployment SEO checklist for developers, because a template change is the usual way the attribute goes missing.

Quick answers

Do same-origin fonts need crossorigin? On a preload, yes. That’s the whole point of this post.

Does @font-face need a crossorigin attribute? No. There’s no attribute in CSS; the browser applies CORS to font loads automatically. Only the HTML preload and preconnect hints need it.

Is crossorigin="anonymous" different from crossorigin? No. They’re identical.

Can I fix it with a header instead? No. The preload’s mode comes from the HTML attribute. The server header only decides whether a cross-origin font is allowed.

-Sethers

Discover hundreds of SEO Issues in Seconds

Without Monthly Subscriptions

Comprehensive technical SEO analysis powered by ML and 16 specialized modules. Optional AI-powered insights from Claude, GPT-4, or Gemini. Get actionable insights in seconds, and never pay monthly fees again.

Download Free Trial

I use AI to generate images for my posts and for general editing, updates, and ironically SEO purposes. I used to draw all of the images for my personal blog (taleas) myself, but as the volume of content I produce has increased, I've turned to AI tools to help create visuals that complement my writing. I go out of my way to generate images that look strange, and don't represent real people. If you ever want to chat about my use of AI, please reach out.