Smart WebFont loading for better performance

Practically every modern new website gracefully relies on Custom Webfonts. Web-fonts are fonts that are not available in the browser by default. They are fonts that are fetched remotely and rendered by the browser.

Web fonts allow you to build a much better web experience, but these fonts also cause a lot of problems such as invisible text, slow loading time, blocked view and layout shifts. 

I see many developers ignore these issues or maybe keep making the same mistakes just because 'they always did'. That is a shame, because with a good webfont strategy you will avoid many problems. And why not? It is less work than you might think.

Fonts loaded with Google fonts and display: swap font synthesis example Advanced font loadingfont synthesis example

In this article you will learn how to load web fonts faster and smarter. We are going to talk about

  • Use the correct font format
  • Use the correct 'font-face declaration'
  • Preload fonts
  • Avoid invisible text when loading fonts.
  • Prevent reflow / layout shift
  • Advanced font loading techniques

Let's break these points down one by one.

1. Use the correct font format

There are a number of font formats you can use (eot, ttf, woff, svg, woff2). How do you know which one to use? That's simple, at the moment you really only need to support woff and woff2 .
Woff is a font format developed by Google. Woff is already zipped by default, which is partly why it is faster and better than all previous formats. Woff is supported by all modern browsers. Woff2 is a faster, smaller and even better version of the woff format, but is not supported by internet explorer.

2. Declare the font

To use your own font you name it and declare certain properties (including weight and style). It is quite easy to declare a font and you do that as follows:

@font-face {  
   font-family: 'myFont';  
   font-weight: 400;  
   font-style: normal;  
   font-display: swap;
   unicode-range: U+000-5FF 
   src: local('myFont'),
url('/fonts/myFont.woff2') format('woff2'), url('/fonts/myFont.woff') format('woff');
}

In the example above you can see that we use a number of style attributes that influence how the font is loaded and displayed.

  • font-family declares the name we give the font when we refer to it later in the CSS or via JavaScript.
  • font-weight is the thickness of the font. 400 is normal, everything above it is bold and everything underneath it is thin text
  • font style indicates the style, this can be normal, italic or oblique.
  • font-display is an important declartion because it descrtibes of how the font shoudl behave during the page load. It determines how long a font remains invisible (block) and when it can be temporarily replaced by an alternative system font when the font takes too long to load.
  • src indicates where the browser can search for the font and in what order.

Now that we know this, we can start making our first smart improvements. There are significant speed gains to be made here on 5 points.

Avoid invisible text when loading

By default, the text of a page (to be specific: text DOM nodes) is hidden on the screen until the font is loaded. The text on the screen remains invisible until the web font is loaded.

As soon as the font is loaded the text will appear on your screen. This is called the Flash Of Invisible Text or the FOIT. Often you want to prevent this Fash Of Invisible Text. Why? Because a blank page with invisible text is not something a visitor is looking for.  Also the FOIT is also often related to important speed metrics. Often the first meaningful paint (FMP) is related to the FOIT. Removing the FOIT from the page will result in and earlier FMP.

Use font-display: swap to direct a browser to replace replace the WebFont with a system font while the font is unavailable. Once the font is fetched and rendered, the system font is replaced with the custom web font. That is called the the Flash Of Unstyled Text or the FOUT. We will come back to this later while discussing soms advanced font loading strategies.

Specify the Unicode range

There are thousands of unicode characters such as 👽 (& # 128125;) that can be used by the browser. The unicode range tells a browser which characters (glyphs) the webfont contains. Remeber, at this point during page load, the font has not been downloaded yet. 

By including the unicode range, a browser knows which font should be downloaded for each character. With no unicode range delared, a browser will try all candidate fonts from top to bottom to check if this character appears in the font.  You can declare a Unicode range for yourself with the Glyphs range generator from Eli Fitch.

Use font synthesis

Lets, for a minute, assume your webpage does something similar to this setup: For normal text your page uses font-weight: 400 and font-weight: 700 for headings. Those means there are 2 fonts to load. At about 20/30Kb per font that should not be an issue. 

But what if there is an italic and bold word in the text? Then, you should add 2 extra fonts to your webpage. Font-weight: 600 for bold text and font-style: italic for italc text. Your fontface set will then just become twice as big making the total font size about 80/120kb.

font synthesis example Font synthesis example

Luckily there is an alternative. If you prefer to, the browser can recreate the italic and bold font by using font synthesis . Font synthesis derives italic and bold text from normal (400) text. Not ideal of course for a designer but a technically smart solution and in some cases a good trade-off.

Shrink fonts with font subsetting

Let's take this one step further. Think about whether you really need all the characters in a font set. For example the 'Ç'. Do you really need it in your headings? Is the answer no? Then you could create a font subset with only the glyphs you need headings via this site font generator. A font subset could reduce the size of your font by more then 50%.

font glyphs These glyphs are standard in a latin font

Set the correct and fastest font order

The order of the fonts in the src attribute can make a world of difference. A browser searches for fonts from top to bottom. When a browser can't find a (local) font or a font  type is not supported, then a browser moves on to the next font. 

The fastest order is as follows: First locally search locally via local ('myfont'). Then the woff2 (remember, this is smaller than woff) and only then the woff. This ensures that we always use the fastest variant of the font. By the way, don't confuse local () with the local browser cache, local looks for locally installed fonts. Often phones already have quite a few fonts pre-installed.

If you really want to use other formats such as eot or ttf, always place these at the back of the src set.

3. 'Preload' fonts

Often you need fonts to be available to the browser as soon as possible. Unfortunately, browsers do not have this prior knowledge. By default, a browser will only download a font when that font is used on the screen (or more specifically in a visible DomNode). To do this, a browser must first anayse the CSS and HTML to ensure that the element with a specific font is visible on the page. Only then will a browser start downloading a font.

Resource hints allow you to adjust that behavior of your browser. Resource hints in the form of rel = "preload" ensure that the fonts are downloaded with high priority. A browser immediately starts downloading a resource when it encounters such a hint. This means you don't have to wait for the CSS and HTML to be analyzed.

To preload a font with resource hints place this code as early as possible in the head of the page, before the CSS and JavaScript references.

<link 
    rel="preload" 
    as="font" 
    href="/my-font.woff2" 
    type="font/woff2" 
    crossorigin="anonymous"
>

In the example above you can see that the link element has different attributes.

  • rel="preload" indicates that the browser should perform a 'full load'. An alternative would be rel = "preconnect". This means that the connection can start an early connection to the server, but that nothing needs to be downloaded yet.
  • as="font"tells the browser that the type is a font. That may seem unnecessary, but if you do not provide this, the browser will probably retrieve the font twice.
  • href="/my-font.woff2" refers to the location of the font-file to be downloaded
  • type="font/woff2" sets the mime type of the font. Just like the as = "font" attribute, this mime type must always be specified. If not, the browser is not 100% sure if the source can be used as a font and will be downloaded again.
  • crossorigin="anonymous" indicates that the font must be retrieved via CORS system because the specifications for retrieving fonts are simply drawn up that way. I can't help it either :-)

Beware Using a CDN:
Now that have made it this far and know the basics of font-loading, it might be a good time to tell you why we are not a fan of externally hosted fonts on CDNs like fonts.google.com . Since Google fonts, adding a web font has never been easier. But it comes at a significant cost to your speed.

Preloading will not be sage. Fonts on Google Fonts are regularly updated. Therefore, you cannot trust that the 'preloaded link  in the head of your page corresponds with the latest version of the font hosten by google. Maybe the Google font CSS file contains a different font-file and eventually 2 versions of the font will be downloaded.
If you really must to use Google font, although we advise against it, you can still save some of the lost time with rel = "preconnect".

We advise hosting your own fonts. Hosting a font yourself is almost always faster. Note that you should use HTTP/2 when self-hosting your fonts. HTTP/2 provides a significant speed boost because fonts can be downloaded earlier and in parallel.

Also more advanced font-loading mechanisms will not work on CDN hosted fonts. These CDN's do not offer any mechanics for subsetting for example. We will come back to that later.


4. Advanced font loading techniques

Now that you know everything you need to know about font loading, we can move on to the more advanced techniques.

Fonts loaded with Google fonts and display:swap font synthesis example Advanced loading techniquefont synthesis example

These techniques contain example code for you to re-use. Place this code in the <head>of the page and see the speed difference.

The 'bare minimum'

With the bare minimum technique makes smart use of the available browser functions such as preloading and font display: swap. This technique is browser-based and is especially effective for loading a small number of fonts (1 to 2), hosted on your own server. 

First make sure the fonts are available as soon as possible with preloading. Then, via the font display: swap, ensure that visitors does not experience the Flash Of Inivisible Text. For returning visitors (who can fetch the fonts at zero latency from their browser cache) this is the fastest technique while for new visitors it gives an acceptably short FOUT

Advantages : This technique relies 100% on the browser and allows the browser to do the heavy lifting. No additional scripts or classes are required for this to work.
Disadvantages : Flash Of Unstyled Text and a Content Reflow for each font.

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

<style>
@ font-face {
    font-family: 'myFont';
    font-weight:400;
    display:swap; /*enabled fallback font and FOUT*/
    src: local('myfont'),url('myFont400.woff2') format('woff2');
}
@ font-face {
    font-family: 'myFont';
    font-weight:700;
    display:swap; /*enabled fallback font and FOUT*/
    src: local('myfont'),url('/myFont700.woff2') format('woff2');
}
body {
    // myFont + fallback
    font-family: 'myFont',sans-serif;
    font-weight:400;
}
h1,h2,h3,h4,h5,h6{
    font-weight:700;
}
</style>


De 'Font with a class'

With this technique fonts are loaded with JavaScript. Once all fonts are loaded a class is added to the page. That class activates your custom web font. For returning visitors a simple  session storage (or local storage) trick speeds up this process.  
Advantages : Relatively easy to apply, requires no changes to the font itself, javascript creates parallel network requests, eliminates FOIT without font display:swap and there is only 1 Flash Of Unstyled Text (FOUT)
Disadvantages : Compared to the 'bare minimum', this is more difficult to maintain and the fonts must be hard-coded somehwere on the page, for example in JavaScript.

<link rel="preload" href="/myFont400.woff2" 
      as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/myFont700.woff2" 
      as="font" type="font/woff2" crossorigin>
<style>
@ font-face {
    font-family: 'myFont';
    font-weight:400;
    src: local('myfont'),url('/myFont400.woff2') format('woff2');
}
@ font-face {
    font-family: 'myFont';
    font-weight:700;
    src: local('myfont'),url('/myFont700.woff2') format('woff2');
}
body {
    // fallback
    font-family: sans-serif;
}
html.fl body{
    // web font
    font-family: 'myFont';
}
</style>
<script>
(()=>{
    if( "fonts" in document ) {
        // Optimization for Repeat Views
        if( sessionStorage.fl ) {
            document.documentElement.className += " fl";
	    return;
        }

        // Load font
        Promise.all([
            document.fonts.load("1em myFont"),
            document.fonts.load("700 1em myFont")
        ]).then(()=>{
            document.documentElement.className += " fl";
            sessionStorage.fl = true
        });
    }
})();
</script>


The '2 stage render' solution

The 2 stage render solution is especially suitable for more heavy font usage that need multiple weights and versions of the same font (300,400,600,700, italic etc).

This method, which builds on the 'Font with a class' technique, quickly load a smaller font with only letters, numbers and punctuation. The smaller font is offcourse created with font syntesis. In the meantime, in parallell the full fonts are fetched by the browser and once they are loaded the are activated with a class.

This technique prevents layout shifts (when a fallback font is swapped with the final font) that could occur every time a font is loaded. Preloading a small font created a Flash Of Unstyled Text extremely early (which is much less noticable then a later FOUT).

<link rel="preload" href="/myFontSubset400.woff2" 
      as="font" type="font/woff2" crossorigin>
<style>
@ font-face {
    font-family: 'myFontSubset';
    font-weight:400;
    src: local('myfont'),url('/myFontSubset400.woff2') format('woff2');
}
@ font-face {
    font-family: 'myFont';
    font-weight:400;
    src: local('myfont'),url('/myFont400.woff2') format('woff2');
}
@ font-face {
    font-family: 'myFont';
    font-weight:700;
    src: local('myfont'),url('/myFont700.woff2') format('woff2');
}
body {
    // fallback
    font-family: sans-serif;
}
html.flsubset body{
    // web font
    font-family: 'myFontSubset';
}
html.fl body{
    // web font
    font-family: 'myFont';
}
</style>
<script>
(()=>{
    if( "fonts" in document ) {
        // Optimization for Repeat Views
        if( sessionStorage.fl ) {
            document.documentElement.className += " fl";
	    return;
        }

        document.fonts.load("1em myFontSubset").then(() => {
            document.documentElement.className += " flsubset";

            // Load font
            Promise.all([
                document.fonts.load("1em myFont"),
                document.fonts.load("700 1em myFont")
            ]).then(()=>{
                document.documentElement.className += " fl";
                sessionStorage.fl = true
            });
        });
    }
})();
</script>

Conclusion

Does your site rely on web-fonts? Then it is definitely worth applying 1 of the 3 advanced techniques. It saves a few seconds of loading time and is relatively easy to implement.  

For assistance with advanced font loading techniques of page speed questions contact us

Free 14-day trial

MarketingTracer smart A.I. tool shows you what you need to rank. Then we'll help you get it done.

Start your 14-day trial >>
  • No credit card required
  • No installation needed
  • No strings attached