Load fonts asynchronously & handle FOUT with my Gatsby plugin

adrianbdesigns

Adrian Bece

Posted on November 3, 2020

Load fonts asynchronously & handle FOUT with my Gatsby plugin

In the past few weeks I was familiarizing myself with Gatsby and I was making a list of plugins that are good-to-have while working on a project. I went through the SEO plugins, SVG plugins, data source plugins, and finally, I wanted to check out some font loader plugins when I noticed something strange.

Here are the top two Gatsby plugins for fonts.

Alt Text

Top two font plugins for Gatsby

And if we open any of those two, we are greeted by the following message and README .

Alt Text

Oh no... this isn't good

Most-used plugins are abandoned, deprecated and not actively maintained.

Don't get me wrong, it's totally okay for authors and companies to back down from developing and maintaining an open-source repo. I think it would be better to transfer the ownership to someone who wants to continue the development beforehand rather than leaving it as is and archiving it.

Asynchronous font loading

That missing support for the most-used plugins was my primary motivation to develop the font loader plugin, but what do font loaders even do?

In short, font loaders help eliminate render-blocking resources, in this case, fonts and their respective CSS files. I've gone into more detail about render-blocking resources in one of my previous articles.

When a website document is loaded, it automatically starts downloading high-priority resources that are needed before displaying the page - CSS, JavaScript, images, iframe, videos, fonts... Depending on how we instruct the browser, we can load some of those resources after the page has been displayed (rendered). By doing this, we are displaying the content as fast as possible to the user and loading all non-critical resources afterward to speed up the loading process.

This is especially true for web fonts, like Google fonts for example. During our page load, we are requesting a font CSS file from Google servers which also requests additional font files from Google CDN. Our page is not displayed until this chain of request resolves which can take some time depending on the CDN performance and user's internet connection.

With asynchronous loading, we can give a low priority to the font file and CSS and load it after the page is displayed. Although this has improved site performance, we have created a minor visual issue - Flash of Unstyled Text (FOUT).

Flash Of Unstyled Text (FOUT)

If the font is loaded after page content is displayed, we can see the moment the font changes between the fallback (default) font and the main web font that has been loaded asynchronously. This event is called Flash Of Unstyled Text or FOUT, for short. This change might even affect the page layout, size of some elements and even cause some visual bugs because the page is styled with web font in mind.

Alt Text

This isn't all that bad considering the performance gain, but it is very noticeable

What we can do to make this effect much less noticeable is:

  • Choose the fallback font that looks as closely as possible to the web font that is being loaded asynchronously
  • Adjust font size, line height, letter spacing and word spacing to match the web font as closely as possible

After adjusting the fallback font CSS, we get the following result.

Alt Text

We've adjusted the style (line-height, letter spacing, word spacing) of fallback font and the change looks nicer

You might be asking: how can we detect when the font has been download and applied to the document?

We'll have to use JavaScript to detect that event. In case of Gatsby, I've written a plugin that both loads the web font asynchronously and it listens to font load event and applies a CSS class to HTML body element to handle FOUT.

Gatsby omni font loader plugin

During the past week, I've been working on creating a Gatsby plugin that will use the recommended way of loading fonts and enable devs to handle FOUT easily.

And few days ago, I've published Gatsby Omni Font Loader that can work with both web fonts and self-hosted fonts, add preload and preconnect on SSR, add font asynchronously, and handle FOUT. All in one small, neat package.

You can check out the source code on Github. Feel free to submit issues, feature requests and pull requests. Support and contribution are very much appreciated!

GitHub logo codeAdrian / gatsby-omni-font-loader

Font loader optimized for maximum performance. Removes render-blocking font resources and loads them asynchronusly. Handle FOUT & FOUC with font loading status watcher. Supports both local-hosted fonts and web fonts.

Omni font loader logo

Gatsby Omni Font Loader v2

  • Simple way to add webfonts or custom fonts to Gatsby project
  • Performant asynchronous font loading can be enabled
  • Font loading listener can be enabled
  • Flash Of Unstyled Text (FOUT) handling support





Features

  • Supports web fonts & self-hosted fonts
  • Preloads the files & preconnects to the URL
  • Loads fonts asynchronously to avoid render blocking
  • Implemented with fast loading snippets
  • Loading status listener for avoiding FOUT
  • Small size & minimal footprint

Install

npm install gatsby-omni-font-loader react-helmet

or

yarn add gatsby-omni-font-loader react-helmet

Configuration

Add the following snippet to gatsby-config.js plugins array.

{
  /* Include plugin */
  resolve: "gatsby-omni-font-loader",
  /* Plugin options */
  options: {

    /* Font loading mode */
    mode: "async",

    /* Enable font loading listener to handle FOUT */
    enableListener: true,

    /* Preconnect URL-s. This example is for Google Fonts */
    preconnect: ["https://fonts.gstatic.com"],

    /* Self-hosted fonts
Enter fullscreen mode Exit fullscreen mode

Installation

Start by installing the plugin with NPM or Yarn.

npm install --save-dev gatsby-omni-font-loader
Enter fullscreen mode Exit fullscreen mode

or

yarn add --dev gatsby-omni-font-loader
Enter fullscreen mode Exit fullscreen mode

Configure the plugin

In gatsby-config.js file, reference the gatsby-omni-font-loader plugin in the plugins array and configure it.

Below is the sample config and explanation for each of the options available.

{
  /* Include plugin */
  resolve: "gatsby-omni-font-loader",

  /* Plugin options */
  options: {

    /* Enable font loading listener to handle FOUC */
    enableListener: true,

    /* Preconnect URL-s. This example is for Google Fonts */
    preconnect: ["https://fonts.gstatic.com"],

    /* Self-hosted fonts config. Add font files and font CSS files to "static" folder */
    custom: [
      {
        /* Exact name of the font as defied in @font-face CSS rule */
        name: ["Font Awesome 5 Brands", "Font Awesome 5 Free"],
        /* Path to the font CSS file inside the "static" folder with @font-face definition */
        file: "/fonts/fontAwesome/css/all.min.css",
      },
    ],

    /* Web fonts. File link should point to font CSS file. */
    web: [{
        /* Exact name of the font as defied in @font-face CSS rule */
        name: "Staatliches",
        /* URL to the font CSS file with @font-face definition */
        file: "https://fonts.googleapis.com/css2?family=Staatliches",
      },
    ],
  },
}
Enter fullscreen mode Exit fullscreen mode

Handling FOUT

When enableListener: true is set in plugin config in gatsby-config.js, HTML classes are being added to <body> element as the fonts are being loaded.

HTML class name format will be in the following format

wf-[font-family-name]--loaded
Enter fullscreen mode Exit fullscreen mode

You can use the Font Style Matcher to adjust the perfect fallback font and fallback CSS config and use the styles from there.

Here is the example of how body element will look like after all fonts are being loaded (depending on the config).

<body class="wf-font-awesome-5-brands--loaded wf-font-awesome-5-free--loaded wf-staatliches--loaded">
Enter fullscreen mode Exit fullscreen mode

So the CSS will look something like this

body {
 font-family: "Merriweather", Georgia, sans-serif;
 /* default styles */
}

body:not(.wf-merriweather--loaded) {
 /* fallback font (Georgia) CSS config */
 /* line-height, letter spacing, font-size... */
}

body:not(.wf-merriweather--loaded) h1 {
 /* fallback font (Georgia) CSS config */ 
 /* line-height, letter spacing, font-size... */
}

.wf-merriweather--loaded {
 /* web font CSS config */ 
 /* line-height, letter spacing, font-size... */
}

.wf-merriweather--loaded h1 {
 /* web font CSS config */ 
 /* line-height, letter spacing, font-size... */
}
Enter fullscreen mode Exit fullscreen mode

These articles are fueled by coffee. So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.

Buy Me A Coffee

Thank you for taking the time to read this post. If you've found this useful, please give it a ❤️ or 🦄, share and comment.

💖 💪 🙅 🚩
adrianbdesigns
Adrian Bece

Posted on November 3, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related