A web performance blog

by Alex Painter

Why I'm preloading a font even when I shouldn't have to

4 September 2019

When I put this website together, I wasn't going to use any custom fonts.

Some kind of performance hit, even a small one, is almost inevitable, and I wanted it to be fast.

However, in the end I decided to use one for the page headers and just make sure any performance impact was minimised.

The first step was to make the font file as small as possible, so I subsetted it. In other words, it contains only the characters needed for the words in the header.

The result was a tiny file – less than 2KB. I also hosted it locally, removing the third-party dependency and the cost of connecting to another domain.

Finally, the font was referenced in inline CSS at the top of the HTML, not in an external CSS file.

This meant that the browser could discover and load it without having to load an external stylesheet – a common cause of late-loading fonts.

A surprise

Because custom fonts are required to display text, browsers should treat them as high-priority assets.

So I was expecting my font file to load fast. I've kept these pages pretty light anyway, so there's not much else that could get in the way.

I was in for a bit of a shock.

When I checked the page in Chrome Devtools and then in Webpagetest, I found that the high-priority font file was loading behind what should have been a lower priority image.

Extract from Chrome Devtools showing images loading ahead of font

This didn't make any sense to me.

I do know that browsers can wait until they discover that a font file is needed before loading it. But the element that uses my font is right at the top of the page, inside the <header>, well before the images that loaded ahead of it. It's even higher up in the DOM tree.

Preloading to the rescue

Preload tells the browser that it needs to load an asset on the current page.

It's a good way to load important resources early when they would otherwise be discovered late.

Fonts are a great example. If a font is referenced inside an external stylesheet, it won't be discovered until that stylesheet has loaded.

Preloading the font gives the browser notice that it's going to need it.

It is also very simple to implement in a <link> element.

<link rel="preload" href="myfont.woff2" as="font" type="font/woff2" crossorigin>

Alternatively, you can add the preload directive as a response header (although some servers/CDNs will interpret this as a request to push the resource unless you specify otherwise).

I wasn't expecting to use preload on this site. My CSS is pretty minimal and I've inlined it in the HTML, so that pages will render faster on a first visit. Since my font doesn't depend on an external CSS file, it should be discovered early anyway, without the help of preloading.

However, I found that preloading the font restored it to its rightful place ahead of the images.

Here are the waterfall charts from Webpagetest – I've highlighted where the font loads with and without preloading:

Waterfall chart showing how preloading a font ensured that it loaded ahead of images

Perhaps more telling, though, is the filmstrip comparison (this was tested on a very slow connection to illustrate the point), with the bottom filmstrip showing the benefit of using preload:

Filmstrips showing the benefits of preloading a font

As an aside, I was actually using font-display: fallback, so that the header was initially displayed in a fallback font if the custom font was slow to load. However, this would have made the two filmstrips very difficult to tell apart in this post, so it was disabled for this test.

Why did this work?

In order to display a web page, the browser needs to build the Document Object Model (DOM) tree and CSS Object Model (CSSOM) tree, which it then uses to construct the render tree.

Adding the preload link for the font appears to allow the browser to start building the CSSOM earlier, before the DOM is completed.

You can see this in the extracts from Chrome Devtools below.

First, without preloading:

Without preloading, the DOM is built, the CSSOM is built, then the font is requested

Now, with preloading:

With preloading, the browser starts building the CSSOM and requests the font before completing the DOM

Another alternative?

If I hadn't been able to use preload to boost the font's priority, how about reducing the images' priority?

Out of curiosity, I did also try applying loading="lazy" to the images on the page. As of Chrome 76 (at the time of writing, there is no other browser support) this should reduce the priority of images outside the initial viewport.

A quick check in Devtools suggested that this did indeed allow the font to load earlier – perhaps something to bear in mind as browser support for the feature grows.

Variable browser behaviour

I tested a few different browsers and devices and found that iOS Safari (iPhone 8) displayed the same behaviour as Chrome – a late-loading font that could be made to load earlier by adding preload.

The late-loading font issue also cropped up in Firefox, but preload isn't yet supported by default in Firefox, so adding it made no difference.

In Edge too, the font file loaded late. Adding preloading seemed to work in Webpagetest – to a point. It actually appeared to cause the font to be loaded twice (once early, once late).

I say appeared to load because, on trying to verify this in Developer Tools in my own version of Edge, the duplicate download was gone. And preloading no longer seemed to get the font to load any sooner. So if there was a bug, perhaps it has been fixed, albeit at a cost.

The point to take away

Browsers don't always behave as we would want or expect them to, but we do have tools at our disposal to steer that behaviour towards the result we want.

I had thought that preloading the font on this site would be completely pointless. So, if there's a lesson for me, it's never assume anything!

tl;dr

Preloading fonts is a great way to get text to display faster on a web page.

This post looks at why it's a good idea to preload fonts, even if they're referenced in inline CSS.