Everything You Never Wanted to Know About CORS and Font Preloads

Futurama Blernsball Player Gets a Pie in His Face, Having Expected to Catch a Blernsball. Caption: WHEN YOU PRELOAD FONTS WITHOUT CROSSORIGIN

CORS and font preloads – it’s complicated. When fonts are preloaded via <link rel="preload" as="font">, we must always include the crossorigin attribute. If we forget it, unexpected issues may arise as we will see later. But there are scenarios where problems cannot be avoided – even if best practices are followed. But let’s start at the beginning:

  1. What is CORS?
    1. The crossorigin Attribute
    2. Safari’s Peculiar Behavior
  2. The vary: Origin Dilemma
    1. The Scope of the Issue
    2. A Real-World Example
    3. Live Demo of the Problem
  3. How Browsers Can Improve

What is CORS?

In simple terms, CORS is a protocol that lets webservers control which resources are allowed to be downloaded from other origins (different domain, port or protocol). The webserver declares in HTTP headers which origins are allowed to download given resources. If you want to know more about CORS and its origin (pun intended), check out this great article be Jake Archibald

In the context of this post it’s important to understand that – according to CSS specification – font requests shall always be made with CORS. This also applies to when the requested font is hosted on the same origin (same-origin). In such cases it adheres to the CORS same-origin policy. Why this is the case is not easily explained. But it is what it is.

Also important to know: CORS requests will come with an Origin HTTP header, where the value is set to the origin that initiates the request. This header can be used to determine if the request is allowed or not.

The crossorigin Attribute

Requests initiated by preloads will only be made with CORS if we explicitly demand so. This is done via the crossorigin attribute, which will classify the request as CORS and also sets the associated credentials mode. Since, as noted, font requests must always be made with CORS and while preloads are (currently) performed without CORS by default, the crossorigin attribute is required for font preloads.

Safari’s Peculiar Behavior

Only Safari deviates from the specs and does not load fonts with CORS when initiated from CSS. This has resulted in ongoing discussions. The arguments against following the specs are a bit hard to grasp, but again, it is what it is. Try it yourself.

While this deviation does not cause harm in most circumstances, it can lead to severe issues when combined with font preloads.

The vary: Origin Dilemma

Sometimes it’s necessary to include a vary: Origin header in the response to a font request. For instance, when we want to allow loading of a font from more than one origin, but not all origins.

Unfortunately, we are now in a scenario where we can’t preload fonts without causing caching issues. There is currently no way to satisfy all browsers.

Case 1: Preloading with CORS

If we follow best practice and set the crossorigin attribute, Safari causes problems. The font request initiated by the preload now comes with CORS and hence the Origin header. The request initiated by CSS will not be CORS and will not have the Origin header. Now the browser behaves as follows:

Step 1: Surprisingly, on initial page loads the preloaded font will be served from the preload-cache, when it’s needed in CSS. It seems that the vary: Origin header can’t be considered because the response doesn’t arrive in time.

Step 2. On reload the font is in the HTTP Cache. When CSS needs it, it now knows that the Origin doesn’t match. It downloads the font again.

On the next reload step 1 kicks in again. The preload causes another download because of an Origin mismatch with the HTTP cache entry. We are in a cycle where step 1 and 2 are repeating endlessly. Every page reload causes another font download.

Case 2: Preload without CORS

This case is even worse because it affects all Chromium-based browsers and it causes the affected fonts to be downloaded twice on every page reload.

The preload and the following request from CSS result in alternating requests regarding the Origin header. Each request cannot serve the file from the HTTP cache because of the Origin mismatch. Instead it downloads the font anew and saves the response in the HTTP cache. This download happens twice per affected font on every page load.

The Scope of the Issue

Here are some numbers to quantify the impact:

  • 11% of all websites use font preloads for at least one font.
  • An HTTPArchive BigQuery analysis shows that 3.38% of font requests include a vary: Origin response header.
  • 98.5% of <link rel="preload" as="font"> elements specify a crossorigin attribute.

Combining these statistics, we can estimate that Case 1 (CORS preload with vary: Origin) affects roughly 1 in 273 websites, while Case 2 (non-CORS preload with vary: Origin) is less common, affecting 1 in 17,930 websites. That might seem insignificant, but major platforms can still be impacted — as demonstrated in the next section.

A Real-World Example

Recently, I came across this exact issue while testing the Bluesky web app. The problem became apparent when I used a fun tool by Brian Louis Ramirez that was introduced in the Web Performance Calendar.

It turned out that Bluesky was preloading their webfonts without CORS and those fonts answered with a vary: Origin header. The scenario described above appeared and in this specific case caused more than 1MB of data uselessly being transferred on every page load, including refreshes. Purely because of a missing attribute, one that is called crossorigin and counter-intuitively should have been set for same-origin fonts.

The consequences of a Missing Attribute

In addition to the decreased font-loading performance the mistake had a substantial impact on carbon emissions.

  • The issue was present for around three month (Oktober – Dezember 2024). In this period the website das 350 million visits according to similarweb.
  • Roughly 77% of visitors were using Chromium-based browsers 18% were using Safari.
  • Calculating conservatively, we assume that no page refreshes were done during visits and that 10% of those visits came from first-time visitors.
  • On Chromium the bug caused 505KB of extra data transfer for first-time visitors, for repeated visitors the bug caused 1.01MB of extra data transfer. Ironically, on Safari the bug saved data transfer. For repeated visitors it saved 505KB of extra data transfer.
  • The numbers above lead to 256 Terabytes of unnecessary data transfer on Chromium-based browsers and 30 Terabytes of saved data transfer on Safari.
  • With the open-source library co2.js this leads to 28 tonnes of CO2e being emitted uselessly.

Live Demo of the Problem

I prepared a small live demo. Check it out with different browsers. On click on one of the buttons below we can reload an iframe. It preloads a webfont that CSS then tries to use. Info about the HTTP-request(s) is then output in the iframe. The text on the buttons have the following meaning:

  • with/noCORS: Fonts preload will be done with/without crossorigin attribute
  • with/no vary: the font responds with/without vary: Origin header

What I find particularly interesting is that in Chromium-based browsers the font preloaded without CORS will be fetched from the HTTP cache if no vary: Origin is involved.

Another nice surprise was to see that Firefox preloads fonts with CORS even when no crossorigin attribut is set.

How Browsers Can Improve? Be like Firefox!

Browsers should default to the mode of the requested resource type when doing preloads. In Chromium-based browsers this means, that font preloads should automatically be done with CORS. An according proposal has been submitted for the HTML specifications. Implementing this suggestion would eliminate potential problems in Chromium. I really hope the proposal will be accepted this time around – unlike 10 years ago when a similar one was rejected.

For Safari, however, this change would not eliminate the issue, because in Safari’s case the crossorigin attribute that is explicitly set would have to be ignored. I don’t think that this would be intended in the proposal mentioned above. Instead, Safari should make font requests with CORS like all the other browsers do. Together with the proposal above this would eliminate the described issues, it would follow the specification and ensure consistency between browsers.

In short: Chromium and Safari should follow Firefox’s example. Firefox correctly initiates font requests with CORS and executes font preloads as CORS requests, regardless of the presence of a crossorigin attribute. Try it yourself using the live demo above.

  • The question of why the crossorigin attribute is necessary for same-origin font preloads has been addressed in detail on Stack Overflow. . However, tracing back why this requirement was made in the first place is challenging. If I understand W3C discussions correctly, it was more an implementation decision than a deliberate security measure. Arrow Up Right
  • One argument is that font services have used the CORS mechanism as a form of DRM, which conflicts with the original purpose of CORS. Arrow Up Right
  • Browsers allocate only a single HTTP cache entry per URI. The vary header is only used for validation: if the vary values of the cached resource and the requested resource do not match, the cache entry for that URI is overwritten. The caching behavior is difficult to improve, and a proposal to fix the handling of vary: Accept in Chromium was rejected. Arrow Up Right
  • The tool generates a sound trace of all requests that occur during a page load. Each request will make a beeping sound that lasts as long as the loading of its response takes. On a refresh of https://bsky.app I heard that a few requests took much longer than all others. Arrow Up Right
  • Calculation with the co2.js (v0.16) library method perVisitTrace(1010*1000, false, {dataReloadRatio: 0.5, firstVisitPercentage: 0.9, returnVisitPercentage: 0.1}); this results in 0.116g per page view. Multiplied with the 270 Million Chromium-based visitors this makes 31.31 tonnes. Analogue calculations for Safari lead to 3.3 tonnes of savings. Arrow Up Right
  • This only works if the response to the no-CORS preload contains the correct Access-Control-Allow-Origin header. Arrow Up Right
  • This topic was already debated during the draft of the preload specification nearly a decade ago. The general feedback on the suggestion was a “no” to implicit crossorigin behavior. The only rationale I could identify in the discussion was that silently shifting from no-CORS to CORS seemed too “magical”. Arrow Up Right
Tagged #webperf

More Posts

6 browser windows containing symbols: a tree, an icon of a heart hovering above two hands, an accessibility icon, a lock, a heart monitor and normal web content
WRAPS - Non-Government-Organisations (NGOs)

For the third part of the WRAPS series, I looked at the websites of well-known NGOs focusing on environmental and climate protection. he results show that even climate organisations do not have sustainability on their radar when it comes to web development.

The meme of two stands where one has a huge queue the other doesn't have any visitors. The headline reads 'DIGITAL PRODUCTS'. The busy stand says 'US product collecting all your data' the empty one says 'EU product respecting your privacy'
US Big Tech Products Should Be Avoided. There Are Good Alternatives.

Sticking to US products make us dependent on, be influenced by, and expose private data to an increasingly hostile opponent.

Bart Simpson, looking depressed, says
Takeaways From This Year's Web Almanac

This year's Web Almanach shows some interesting developments in the web. Not all of them encouraging, though.