Load fonts asynchronously & handle FOUT with my Gatsby plugin
Tags: react, gatsby, webdev, javascript
Published: 2020-11-03 06:42:35

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.

<figure>
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/brx26n7mtszd9ds2w3em.PNG)
<figcaption>
Top two font plugins for Gatsby
</figcaption>
</figure>

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

<figure>
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kwa9qfvynohcsulgya71.PNG)
<figcaption>
Oh no... this isn't good
</figcaption>
</figure>

**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.

{%link https://dev.to/prototyp/improving-website-performance-by-eliminating-render-blocking-css-and-javascript-28ei %}

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.

<figure>
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cblxyti7vjq5ms3t5lk9.gif)
<figcaption>
This isn't all that bad considering the performance gain, but it is very noticeable
</figcaption>
</figure>

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.

<figure>
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kwjrk9kwln3ybqjsd352.gif)
<figcaption>
We've adjusted the style (line-height, letter spacing, word spacing) of fallback font and the change looks nicer
</figcaption>
</figure>

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](https://www.gatsbyjs.com/plugins/gatsby-omni-font-loader/) 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](https://csswizardry.com/2020/05/the-fastest-google-fonts/) and enable devs to handle FOUT easily.

And few days ago, I've published [Gatsby Omni Font Loader](https://www.gatsbyjs.com/plugins/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](https://github.com/codeAdrian/gatsby-omni-font-loader). Feel free to submit issues, feature requests and pull requests. Support and contribution are very much appreciated!

{% github codeAdrian/gatsby-omni-font-loader %}

### Installation

Start by installing the plugin with NPM or Yarn.

```bash
npm install --save-dev gatsby-omni-font-loader
```

or

```bash
yarn add --dev gatsby-omni-font-loader
```

### 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.

```js
{
  /* 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",
      },
    ],
  },
}
```

### 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
```

You can use the [Font Style Matcher](https://meowni.ca/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).

```html
<body class="wf-font-awesome-5-brands--loaded wf-font-awesome-5-free--loaded wf-staatliches--loaded">
```

So the CSS will look something like this

```css
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... */
}
```


<hr />

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.

<a href="https://www.buymeacoffee.com/ubnZ8GgDJ" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-red.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>

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