Frontend performance: fast web fonts

Psst: we are hiring remote frontend developers and backend developers.

Illustration of a person holding a laptop and a person holding a picture of cat

Fonts are a crucial part of every app or website and they can also play an important role in the frontend performance. Using a fast eCommerce api does not guarantee a fast experience for the user, especially if you are using external fonts the wrong way. Ensure that your fonts load fast so that your customers are experiencing a super fast eCommerce. Read on to find out how to reduce the font byte footprint up to 90% to improve site speed and FCP.

Motivation

With the evolution of web browsers, new features are coming every day and today I would like to talk about one that, although small, it may impact the performance of the website/app.

I strive to improve the performance of the website or application that I’m working on and based on that, I’ve come across with this great article at Smashing Magazine about "Optimizing Google Fonts Performance" by  Danny Cooper and so I ran some tests on my own to see results and take my conclusions.

The first thing that I’ve done has to use the browser console to see the sizes of each download and that way see if it will really make an impact.

For the test, I’ve used the Roboto font, since it’s one of the most used fonts and it was a lot of variants, 12, to be more precise! Being them:

  1. Thin
  2. Thin Italic
  3. Light
  4. Light Italic
  5. Regular
  6. Regular Italic
  7. Medium
  8. Medium Italic
  9. Bold
  10. Bold Italic
  11. Black
  12. Black Italic

And the API that I’ll use for the test, is the Google Fonts API.

The tests

All right, so let’s get started.

First, I’ve started by asking for the font family Roboto: 

fetch("https://fonts.googleapis.com/css?family=Roboto")

Which turn to be the normal font style with the weight 400 for the following languages:

  • latin 
  • latin-ext 
  • vietnamese 
  • greek 
  • greek-ext 
  • cyrillic 
  • Cyrillic-ext

The measurements I’ve got were:

Then, let's try with all of the 12 variants.

fetch("https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i")

Now, let’s say I only need the variants regular (400) and the bold (700) because it will be the only ones that I’ll use:

fetch("https://fonts.googleapis.com/css?family=Roboto:400,700")

So, it’s possible to see the bundle sizes and the times to download them, are decreasing! 

That’s awesome. Although that is one more thing that we can do. Let’s say that I’ll use only the Roboto font for the title of the website/application. Then I could just ask for those characters and reduce even more the bundle size and time to download:


fetch("https://fonts.googleapis.com/css?family=Roboto:700&text=xcompany")

So what is happening here? Let’s try to make things more clear. So for each variant, I ask the API, the response coming is something like this:

@font-face {  font-family: 'Roboto';  font-style: normal;  font-weight: 700;  src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/l/font?kit=KFOlCnqEu92Fr1MmWUlvBh0_IsHAncsWGhbabilm&skey=c06e7213f788649e&v=v19) format('woff2');}

And as you can see, there is a new HTTP request coming with the font-face so the browser still needs to fire another request to get the font! And that’s why the time, vary for each request although the resource size is smaller.

Now imagine that we need to fire this for each font variant… And the request will be sequential, meaning they will only be fulfilled after the previous is completed!

To speed up things more and don’t block every request, there are two things that can be done.

DNS prefetch and preconnect

DNS prefetch and preconnect scripts that will allow the browser to start the connections with Google Fonts API as soon as the page begins to load!

<link rel="dns-prefetch" href="//fonts.googleapis.com">

And the 

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

This will allow the browser to start the additional necessary requests for each font variant at the same time as the first one starts, instead of waiting until the first request finishes to start the other ones!

@font-face descriptor (currently defined as font-display) that allows control over how a downloadable font renders before it is fully loaded.

Cool! Now that I’m getting the fonts from the Google Font API without blocking the requests, I only have to rely on the internet connection and hope that the requests will be fulfilled.

Question: While the browser is retrieving the font(s), will the user see something? Some text?

Well, maybe not but I can solve that with one thing: display=swap. All I need is to add this query parameter to the request and the modern browsers will swap the font to the system default while the specific font is being loaded!

Google Fonts API will return this:

@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

See the font-display: swap? That is what will tell the browser to swap the font to the fallback while fetching the Roboto one.

Go to Can I Use and search for font-display, we’re presented with this description:

The font-display descriptor determines how a font face is displayed based on whether and when it is downloaded and ready to use.

And all the major browsers already supported it.

This is a great feature, or at least for me was the wow factor, because users will have some context to see, while the specific font is being loaded.

This is all cool but one could ask if wouldn’t be better to keep the fonts I want and serve them myself? 

We can. And previous I was doing it this way but then I read more about it and realise I would have more work on my end, like keep the fonts up-to-date, check for new releases and keep several font formats for different browsers… 

Why doing this while the browsers can do it for us?

The Google Fonts API at the time of font request will check out the user-agent to know what is the best font format that can send back. 

Based on that, the API will send the highest compressed format of the font which can save some bytes, whether is a woff2, woff, eot or ttf format! And it will also provide the most up-to-date font. 

The only thing that can go wrong is that the request is not fulfilled because of the internet connection (or lack of it) but the browsers keep fonts in the cache, so they can use them, or they will use the fallback since the font-display will swap!

Async load fonts

There is a trick that can be done to have the fonts being loaded asynchronously, to avoid chaining request, as can be found in the Page Speed Insights from Google.

<link href="https://fonts.googleapis.com/css?family=Roboto+Slab:700|Roboto:400,500,700,900&amp;display=swap" rel="stylesheet" media="print" onload="this.media='all'">

It's simply having the link to the Google fonts API set to media "print" and change it as soon as the load finishes to "all" since we want the styles to be applied to the screen environment. That is done with the `onload="this.media='all'"` the `onload` attribute will be executed immediately after a page has been loaded. Resulting in no chaining requests!

Conclusion

In conclusion, it’s perfectly clear that considering the download of the Roboto font (weight 400) vs (weight 700 and the needed text) we are facing a reduction of 88.13%! And even more when going with (weight 400 and 700), 94.2% reduction. As you can see, it has a significant impact on frontend performance. Improving your site speed and specifically first contentful paint (FCP).

Every byte matter and this is something we value at Crystallize, because...

Milliseconds matters!