Best practices for including webfonts

Best practices for including webfonts

For a project for our client Staatsloterij we use two variants of the Google Font Montserrat. Those had to be included in a webpage. How hard can it be? I discovered that the way you include fonts can harm the rendering performance. Below are the results of my research.

Tl;dr

There is no best option, because each option has its own side effects. The best option for your project depends on what you consider important.

Option 1: I have webfonts on my own CDN and I want to have the text rendered when my webfont is loaded. I don’t care about a flash of invisible text.

You get a timeline like this:

Source:

Font Loading Revisited with Font Events | Filament Group, Inc., Boston, MA
Filament Group helps companies design and build super-fast responsive sites and web apps that are simple to use and…www.filamentgroup.com

Include the paths to your fonts in @font-face in your stylesheet. The fonts are downloaded in the background and will render when they are loaded. The texts will be invisible in the time between the page render and the render of your font. This can harm the perceived performance of your page. A website is useless, in most cases, without the available text. Each browser has a fallback to the default browser font for the case that your fonts never show up. This timeout depends on the browser.

Option 2: I have webfonts on my own CDN and I want to have text rendered as fast as possible. I don’t care about a flash from the default browser font to my webfont.

You get a timeline like this:

Source: https://www.filamentgroup.com/lab/font-events.html

Include the fonts in your stylesheet, just like option 1 and add some more css:

body { 
  font-family: sans-serif; 
}

.fonts-loaded body { 
  font-family: 'Montserrat';
}

Also include the Font Face Observer script (2kb gzip). This script observes your fonts and has an event when the fonts are loaded. You can respond on that event by setting the classname ‘fonts-loaded’.

var Regular = new FontFaceObserver('Montserrat', { 
  weight: 400 
});
var Bold = new FontFaceObserver('Montserrat', { 
  weight: 700 
});

Promise.all([Regular.check(),Bold.check()]).then(function () {
    document.documentElement.className += ' fonts-loaded';
});

The advantage of this approach is that a user can read text on the page directly after rendering, but it is in the default browser font. Your fonts will replace the default browser font when loaded. More detail in this blogpost of the Filament Group.

Combination option 1 & 2: I have webfonts (base64 encoded in a stylesheet) and I want to have text directly rendered in my webfont.

Include the fonts in your stylesheet as a base64 encoded string (for example: a .ttf file). Your stylesheet will be bigger in size and the base64 string needs to be decoded to your font, so it takes longer before the rendering start compared to option 2. The good part is that a user can read text on the page directly after rendering in your webfont. It depends on the difference in render time whether this delay is acceptable for you.

Option 3a: I have a Google font and I want to have the text rendered when the Google font is loaded. I don’t care about a flash of invisible text.

Google fonts are usually hosted on their servers. The path to the actual font files contains a version number and a hashed filename. So linking to the actual font file is tricky; option 1 & 2 were not possible in this case.
You can implement 1 of the first 2 options that Google provides you:

<link href="https://fonts.googleapis.com/css?family=Oswald" rel="stylesheet" type="text/css">

Or:

@import url(https://fonts.googleapis.com/css?family=Oswald);

There won’t be a flash from the default browser font to the Google font with this implementation. The text will become visible when the font is loaded, so the perceived performance could be slower (see option 1). A disadvantage of this approach is that it is render-blocking. A stylesheet is loaded synchronously by default. Rendering start when the stylesheet is downloaded from Google. The Google CDN is fast, in most situations, but the rendering can start earlier when the stylesheet is loaded asynchronous (see option 3b).

Option 3b: I have a Google font and I want to have the text rendered when the Google font is loaded. I don’t care about a flash of invisible text.

Use option 3 which Google provides you.

Steps:

WebFontConfig = {
    google: { families: [ 'Oswald::latin' ] }
  };

(function() {
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
    '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
  })();

This script downloads the WebFontLoader, the stylesheet and the fonts. The script is downloaded asynchronously, so it doesn’t block rendering. This option still got the disadvantages of option 1.

Option 4: I have a Google font and I want to have a directly readable page in a webfont. I don’t care about a jump to the Google webfont when it is loaded.

This option looks like option 2. So we add some CSS and the Font Face Observer script:

body { 
  font-family: sans-serif; 
}

.fonts-loaded body { 
  font-family: 'Montserrat'; 
}

var Regular = new FontFaceObserver('Montserrat', { 
  weight: 400 
});
var Bold = new FontFaceObserver('Montserrat', { 
  weight: 700 
});

Promise.all([Regular.check(),Bold.check()]).then(function () {
    document.documentElement.className += ' fonts-loaded';
});

Instead of including a stylesheet on the default way (synchronous), add the stylesheet via javascript to the head (asynchronous).

<body>
  ...
  
    var head = document.head
    , link = document.createElement('link')
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.href = 'https://fonts.googleapis.com/css?family=Montserrat:400,700&subset=latin';
    head.appendChild(link);
  
</body>

This approach makes sure that loading your custom webfont isn’t render-blocking. It has the same advantages and disadvantages as option 2.
You can use WebFontLoader from Google in option 3 as well. The cons of this approach are size (2kb vs 6kb) and you first have to do a call to get this script.

Combination option 3 & 4: I have Google fonts (base64 encoded in a stylesheet) and I want to have text directly rendered in the Google font.

As mentioned in option 3a, it is tricky to reference a font file with a version number in the path and a hashed file name. But you can download the fonts and convert it to a base64 encoded string.
Note: Google provides you with the fonts you need for each browser. So in the latest Chrome you get .woff2, in IE9 you get .woff.

Add your base64 strings to @font-face in your stylesheet. Your page will become readable directly after rendering with the text in the Google font. Caching of the webfont is done by caching the stylesheet. This approach has the same disadvantages as the combination of option 1 & 2. Also, if Google updates the font, you don’t get those updates automatically.

Below is a test with WebPageTest.org loading 2 Google Webfonts over 3G. Top: combination option 3 & 4, bottom: option 4.

0 sec -> 4.5 sec

My conclusion

On this project I went with option 4. For me it’s important to have the text rendered and readable as fast as possible. I’ll keep in mind the combination of option 3 & 4, depending on the evolution of the size of the stylesheet.

I don’t know how it works with other webfont CDN’s like Typekit.

Add-ons or improvements are welcome!


Check out our Engineering Blog for more in depth stories about pragmatic code for happy users!