Fast Web Fonts for Better Website Performance
Fonts are a crucial part of every app or website and play a significant role in front-end performance. However, making sure you have a fast web font is a practice usually overlooked.
Using a fast eCommerce API alone does not guarantee a fast experience for the user, especially if you use external fonts incorrectly. Besides a better user experience overall, optimizing page performance and designing loading fonts you use fast is necessary.
One of the most recent studies found that the median font file with an English-only subset of characters should be around 12K (source).
What does that mean for you? and how can you do that? Well, read on to find out how to reduce the font byte footprint up to 90% to improve site speed and FCP. And I'll share the best practices for font optimization.
With the evolution of web browsers, new features are coming every day, and today, I would like to talk about one that, although small, may impact the performance of the website/app.
I always strive to improve the performance of the website or application I’m working on. I recently came across a great article at Smashing Magazine about "Optimizing Google Fonts Performance" by Danny Cooper. So, based on this, I ran some tests on my own to see the results and draw my own conclusions.
The first thing I did was use the browser console to see the sizes of each download and see how it would impact. I’ve used the Roboto font for the test since it’s one of the most used fonts ATM, with many variants, 12, to be more precise! Being them:
- Thin
- Thin Italic
- Light
- Light Italic
- Regular
- Regular Italic
- Medium
- Medium Italic
- Bold
- Bold Italic
- Black
- Black Italic
And the API that I’ll use for the test is the Google Fonts API.
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 of 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 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 they 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. However, that is one more thing that we can do. Let’s say I’ll use only the Roboto font for the title of the website/application. Then I could ask for those characters and reduce the bundle size and time to download even more:
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, and the response 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');}
As you can see, a new HTTP request is coming with the font face, so the browser still needs to fire another request to get the font! And that’s why the time varies for each request, although the resource size is smaller.
Now, imagine that we need to fire this for each font variant. The requests will be sequential, meaning they will only be fulfilled after completing the previous one!
To speed up things more and don’t block every request, two things can be done.
DNS prefetch and preconnect scripts will allow the browser to start the connections with Google Fonts API as soon as the page loads!
<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 the requests will be fulfilled.
Question: When the browser retrieves 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 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 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 are 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, it 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 it wouldn’t be better to keep the fonts I want and serve them myself.
We can. Previously, I was doing it this way, but then I read more about it and realized I would have more work on my end, like keeping the fonts up-to-date, checking for new releases, and keeping 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 the best font format that can be returned.
Based on that, the API will send the highest compressed font format, which can save some bytes, whether it 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 that they can use them, or they will use the fallback since the font-display will swap!
There is a trick that can be done to have the fonts loaded asynchronously to avoid chaining requests, 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&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 changing 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 that will be executed immediately after a page has been loaded, resulting in no chaining requests!
Optimizing fonts is a crucial component of an overarching performance strategy. There is not a single font optimization technique that can solve all of your problems. What follows are best practices we've found time and time again to work in most cases.
Utilize Moderation. Use custom fonts sparingly to balance aesthetics and page load time.
Mobile Optimization. Reduce font load on mobile devices using HTML media attributes and CSS @media queries to conserve page weight on slower networks.
Employ System Fonts. Opt for system or web-safe fonts, which don't require additional HTTP requests, thereby boosting page speed.
Asynchronous Font Loading. As mentioned, apply asynchronous loading techniques to prevent render-blocking, ensuring fonts don't hinder page rendering and user interaction.
Font Display Strategies. Utilize font-display property to control how fonts are displayed during loading, enhancing perceived performance.
Self-Hosting Fonts. When possible, self-host fonts to reduce reliance on external resources and control font loading and caching better.
Optimize Font Files. Compress font files and use modern formats like WOFF2 to reduce file size without compromising quality.
Preloading and Prefetching. Again, I talked about it in the above example. Use resource hints preload and prefetch to prioritize loading crucial font files, ensuring they're available as soon as needed.
Font Subsetting. Subset fonts to include only the characters and icons used on the website, reducing the file size and speeding up loading times.
Keep in mind it's not just about performance with fonts. Typography plays an essential role in practical design, branding, readability, and accessibility. It is web fonts that enable these critical aspects.
It’s 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 (weights 400 and 700), 94.2% reduction. As you can see, it significantly impacts frontend performance, improving your site speed and, specifically, first contentful paint (FCP).
In today’s speed-obsessed world, every byte matters.
Follow the Rabbit🐰
Web Safe Fonts
Every web page we visit on the internet has text to explain something, give us indications, or tell us a story. They can also be fundamental in design and branding. As we can see, typography is essential on the web, and it's also something we should consider when building a website or application for many reasons, like availability, readability, and performance.
But first, let's cover some basic fonts theory.
Frontend Performance Measuring, KPIs, and Monitoring
Core Web Vitals, LCP, Cumulative Layout Shift, FID, Webpagetest.org, Google Lighthouse, Pagespeed Insight, CrUX, or the latest one, Interaction to Next Paint (INP) —we're sure you've stumbled upon one of these in your quest to improve front-end performance.
But how do you measure performance? And why do you need to be data-driven and continuously monitor, optimize, and build a performance testing and optimization culture?
Let's explore that. Let's go millisecond hunting.