I recently researched font loading again as I wanted to use a local copy of a font and serve it as fast and smooth as possible. This is a quite different approach to when you use TypeKit or Google fonts and their simple copy/paste snippets…

Over the past months there have been a few articles taking care of different font loading optimization techniques. Reading all of them, I ran into a few other issues that are not covered there. Finally, I wanted to have one resource combining the information of the others. Some code snippets are borrowed or adapted from the articles I linked here.

The Goals

  1. Load web fonts asynchronously
  2. Avoid big reflows in layout
  3. Load web fonts as fast as possible
  4. Avoid loading web fonts for recurring visitors

Now let’s try to reach our goals step by step:

1. Load webfonts asynchronously

As you have to call the web font locally via @font-face you usually have a blocking request. To avoid this, you need to put the @font-face code into a separate stylesheet that you call via JavaScript. There is the simple loadCSS function from filamentgroup you can use. This is the most basic variant.

You can also use typekit’s webfontloader with custom web fonts which since version 1.6.8 uses native Font Loading API if available.

But now we also have the Font Face Observer, then we have a Font Loader making use of the fresh W3C Font Load Events and we have a script which Smashing Magazine makes use of which actually solves a few more of this article’s points (but not all).

So where do we start now? First, we exclude the Font Loader providing a polyfill for the latest web standard. It is too large to have this in as part of a performance optimization step. It will definitely be cool in future to use this but unless natively available it doesn’t make sense to use it. Instead, we can use Font Face Observer in the meantime. It only weighs in at 5kb (minified, not gzipped).

Cool, let’s start. You can get the Font Face Observer script via npm, bower or git(hub). Include the script into your <head>.
Note: Setting the async attribute randomly adds an unwanted FOIT additionally to the FOUT to your website.

Now we need to initialize the font face observer. You can do this with an inline script or with an external script included in the <head>. I’m showing the demo using the browserify / CommonJS variant in an external script and loaded the script as npm dependency:
First, we need to require the library:


    var FontFaceObserver = require('fontfaceobserver');

Now let’s define the fonts and font variants we want to observe:


    var observer = new FontFaceObserver('Fira Sans', {
        weight: 400
    });

You can use weight, style, variant, featureSettings, stretch here as options. Now we want to initialize the observer on our defined font settings. We still need to set the CSS class that the font has been loaded. That is done via the following Promise:


    observer.check().then(function () {
        document.documentElement.classList.add('webfont-loaded');
    }, function () {
        console.info('Web font could not be loaded in time. Falling back to system fonts.');
    });

A more advanced example with multiple web fonts

Let’s add this with a more complex set of web fonts and styles which is more like we would use it in normal projects:


    var fontfaceobserver = require('fontfaceobserver');
    var fontObservers = [];
    var fontFamilies = {
        'Fira Sans': [
            {
                weight: 400
            },
            {
                weight: 600
            }
        ],
        'Fira Mono': [
            {
                weight: 400
            }
        ]
    }

    Object.keys(fontFamilies).forEach(function(family) {
        fontObservers.push(fontFamilies[family].map(function(config) {
            return new FontFaceObserver(family, config).check()
        }));
    });

    Promise.all(fontObservers)
        .then(function() {
            document.documentElement.classList.add('webfont-loaded');
        }, function() {
            console.info('Web fonts could not be loaded in time. Falling back to system fonts.');
    });

With that, we’re done implementing the Font Face Observer and have set the class when the font is ready. Now we should reflect that in our CSS:


    body {
        font-family: sans-serif;
    }
    .webfont-loaded body {
        font-family: 'Fira Sans', sans-serif;
    }

2. Avoid big reflows in layout

We now have a seamless and reliable system to inject our web font when it’s available. That of course means that initially, users will get the fallback font you’ve defined. While this is a good thing because they can already start reading the text, it means users will get a flash and restyling, often resulting in a jump of words due to the font change.

We want to aim for a better experience here. This consists of two steps:

  1. First, you need to find the best fallback fonts to your web font. This is best done with this little bookmarklet and the compatibility check over here. When you’ve found a good fallback font you’re half done. But no font looks the same and even the nearest fallback font has glyphs with a different width or a different x-height. Fortunately, CSS gives us some tools to optimize this. The fresh font-size-adjust property exists to adjust the fallback font to your web font or the other way round. Set this to to adjust the x-height of the typeset. It’s a bit tricky to use so I recommend to try some values in the dev tools (or read up the formula in the linked article if you’re more into maths). Usually you will end up somewhere between 0.40 and 0.70 as value.
  2. After matching the x-height, we now start to adjust the letter-spacing. This is relatively easy and by setting the value in super small steps (i.e. -0.01em) you ensure it still looks good and is readable. Now you should be at a point where you have a very similar font rendering of the fallback font to your web font. To see the difference during testing, you can add a timeout to the function that sets the webfont-loaded CSS class.

3. Load webfonts as fast as possible

This one is relatively simple. In fact we’ve already done much to load our web fonts as fast as possible and inject them at the right time. The last thing you can do now is to add a preload header for the web font files.

Note/Update: I previously stated to use prefetch which is wrong. Instead, you should use preload which is nearly unsupported and has the drawbacks stated below.


    <link rel="preload" href="FiraSans-Regular.woff">
    <link rel="preload" href="FiraSans-SemiBold.woff">

As you can see I only used the woff fonts here. This is because we shouldn’t call all the different formats in here. This little snippet tells the browser that if possible it should download these resources already. If you’d add all formats here, the browser would download them all which is kind of useless. However, all browsers that support prefetch support WOFF, too. If you want to use woff2, this is not true anymore and you should avoid adding these headers.

4. Avoid loading web fonts again for recurring visitors

To avoid that the users have to re-download web fonts over and over again and don’t run the late JavaScript font face observer when the font is already available from cache, we can use cookies or localStorage. While the cookie script only looks if the font is in the browser’s cache, the localStorage variant will put the fonts in the longer lasting localStorage and retrieve it from there if a user visits the site again.

I won’t give a preference here as both have some advantages. While the localStorage will be safer to ensure it’s cached, it’s a bit slower than using cookies and the native browser cache. Use what you prefer and what will probably work better for your project.

This method is somehow unreliable. A browsers’ cache is not directly related to cookie storage so if one or the other is deleted but not both, this method can cause a blocking font load.

While it works pretty good you should consider that using localStorage might still be a bit slower than using the native cache and, what is more important, this fills up the user’s disk space and is only per domain. So if a user visits another site using the same web font, the UA downloads it again.

What’s next?

As we have seen in the past months and years, the way we implement web fonts changes all the time. Browsers will implement the Font Load Events, there will even be CSS properties that help you dealing with the FOIT. font-rendering, font-rendering: swap will hopefully make all the things I described here much easier.

If you want to save some bytes by excluding several styles or weights of the web fonts, you can control the browser’s behavior if faux styles are allowed or not with font-synthesis.


Like it?
Hire me!