JF's Dev Blog

Django, Vue, and other things, too

Improve Page Speed when Using Google Fonts

Note

Thanks to some helpful comments on Reddit, I've updated this post with more information about the Google Fonts stylesheet's features, information about the drawbacks of this method, and alternate ways of loading Google Fonts.

Recently, I started working on a small web application that converts tables copied from spreadsheets and websites into well-formatted Markdown tables. (Check out a pre-launch version at Table to Markdown!) I wanted the site to be minimalist but not too boring, so I opted to use a custom font from Google Fonts to add one piece of flair.

The font I chose is IBM Plex Mono, a monospaced font from IBM. It's easy-to-read and has interesting italics, which makes it a good fit for a Markdown-focused website.

That nice-looking font comes at a price, however: page load times.

Adding a custom Google Font, instead of using an OS-native font stack like Bootstrap does, inevitably increases page load times. Instead of a loading all web resources from your own server, adding a Google Font means that your website now depends on font files from Google's servers, too.

If you've read my post about building this blog with Pelican, you'll know that I like quick page loads. Google's PageSpeed Insights ranks website page load times out of 100, and 100/100 is always my goal.

So, how can a person use Google Fonts without sacrificing too much in page load speed? This post will answer that question, and I hope it will also serve as a blueprint for how to research and improve page load times in general.

Let's start by seeing why Google Fonts slows down page loads, then looking at how we can limit that performance penalty.

Default Google Font loading

When you find a funky font at Google Fonts and you're ready to use it, the instructions look a little like this:

Google Fonts IBM Plex Mono usage pop-up

Easy enough! Add that <link> element to the <head> of your website, and you're ready to use your selected typeface in your CSS font-family rules.

That's just what I did for this HTML document:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>With IBM Plex, Default</title>
  <!-- Default Google Font loading -->
  <link href="https://fonts.googleapis.com/css?family=IBM+Plex+Mono:400,400i&display=swap" rel="stylesheet">
  <style>body { font-family: 'IBM Plex Mono', monospace; }</style>
</head>
<body>
  <h1>This should be in IBM Plex Mono</h1>
</body>
</html>

This webpage doesn't look like much, and that's the idea. Because this HTML contains almost nothing, it will give us a good sense of how much the Google Font is impacting our page loads.

To test how this Google Font impacts page load performance, I used the performance audit in Chromium's DevTools. This audit uses Lighthouse, the same tool that powers PageSpeed Insights.

Here's how you can follow along:

  1. Copy the above HTML file to a directory on your machine, and name it index.html
  2. Launch an HTTP server in the same directory
    • I use Python's http.server: python -m http.server 8000
  3. Visit http://localhost:8000 in Chromium or Chrome
  4. In the developer tools, navigate to the "Audits" tab
  5. Under "Throttling" Select "Simulated Slow 4G, 4x CPU slowdown"
  6. Click "Run audits" at the bottom of the tab

Note

Using "Simulated Slow 4G, 4x CPU slowdown" helps show how a page will load for a user with a mobile device and a data connection. Testing page speed on a fast computer with a fast Internet connection can mask poor performance in real-world scenarios. Throttling exaggerates the performance impact of large file sizes and multiple requests, showing that imperceptable performance improvements for some users can be significant for others.

And here are the performance audit results:

Lighthouse Audit Performance Analysis, default Google Font loading

At first blush, this looks good! After all, the performance is 100/100. But this 100 is a soft 100.

Let's check out the "Diagnostics" section:

Lighthouse Audit Opportunities Section

It says that if we "Eliminate render-blocking resources", we could load this page 0.78s faster. Let's find out what that means and how it can improve page loads.

Google Fonts page speed impact

Using the recommended method to add a Google Font into a webpage can slow a page load by a whopping 780ms.

The audit calls this a "render-blocking resource", which means that the page can't load until this CSS file has been fetched from the Google Fonts server, fonts.googleapis.com.

Is there anything I can do about that? After all, I did opt to use a font from a third-party service. The performance audit answers this question in the "Diagnostics" section:

Lighthouse Audit Diagnostics Section

Huh. To load a font from Google Fonts, the browser has to:

  1. Fetch the HTML (it's hard to show a webpage without HTML)
  2. Fetch a file at fonts.googleapis.com/css
  3. Fetch the font file (with a .woff2 extension) from fonts.gstatic.com (the custom font)

This three-step process is a "Critical Rendering Path", or CRP, between a user's browser and the resources necssary to display a webpage. In this case, there looks to be an extra step that could add 30ms to a page load.

Google provides three recommendations to optimize CRP:

  • Minimizing the number of critical resources: eliminating them, deferring their download, marking them as async, and so on.
  • Optimizing the number of critical bytes to reduce the download time (number of roundtrips).
  • Optimizing the order in which the remaining critical resources are loaded: downloading all critical assets as early as possible to shorten the critical path length.

In short: use fewer, smaller critical resources, and load them as soon as possible.

To see how we can optimize the CRP to load a Google Font, let's see what https://fonts.googleapis.com/css?family=IBM+Plex+Mono:400,400i&display=swap returns:

/* cyrillic-ext */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F6pfjptAgt5VM-kVkqdyU8n1ioa2Hdgv-s.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F6pfjptAgt5VM-kVkqdyU8n1ioa0Xdgv-s.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F6pfjptAgt5VM-kVkqdyU8n1ioa2ndgv-s.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F6pfjptAgt5VM-kVkqdyU8n1ioa23dgv-s.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F6pfjptAgt5VM-kVkqdyU8n1ioa1Xdg.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono'), local('IBMPlexMono'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F63fjptAgt5VM-kVkqdyU8n1iIq129k.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono'), local('IBMPlexMono'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F63fjptAgt5VM-kVkqdyU8n1isq129k.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono'), local('IBMPlexMono'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F63fjptAgt5VM-kVkqdyU8n1iAq129k.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono'), local('IBMPlexMono'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F63fjptAgt5VM-kVkqdyU8n1iEq129k.woff2) format('woff2');
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('IBM Plex Mono'), local('IBMPlexMono'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F63fjptAgt5VM-kVkqdyU8n1i8q1w.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Info

The Google Fonts stylesheet includes the most recent version of the specified fonts and uses a font file format that works in the requesting browser. Performing this request from a different browser or at a different time may return different CSS.

Well, would you look at that? It's just a CSS file with @font-face rules to load IBM Plex Mono font files.

This means that if we use the default Google Fonts way of loading a font, before we even start loading the font files, we have to do an additional request to get the CSS containing links to those font files. The Google Fonts API tells the browser to cache font responses, so subsequent page loads will be faster than this initial load.

Still, what if we could load a Google Font in one request?

Inlined Google Font loading

To load a Google Font in one request instead of two, we need to elimitate the CRP-lengthening stylesheet request.

How? The "Opportunities" section of the Lighthouse audit told us from the start:

Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline [emphasis added] and deferring all non-critical JS/styles.

The CSS that loads our custom font certainly counts as 'critical'. To inline it, we just need to add its CSS contents in a <style> tag in our HTML file:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>With IBM Plex, Inlined</title>
  <style>body { font-family: 'IBM Plex Mono', monospace; }</style>
  <!-- Inlined Google Font loading -->
  <style>
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F6pfjptAgt5VM-kVkqdyU8n1ioa1Xdg.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: local('IBM Plex Mono'), local('IBMPlexMono'), url(https://fonts.gstatic.com/s/ibmplexmono/v5/-F63fjptAgt5VM-kVkqdyU8n1i8q1w.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }
  </style>
</head>
<body>
  <h1>This should be in IBM Plex Mono</h1>
</body>
</html>

By replacing the Google Fonts <link rel="stylesheet" /> with inline CSS, we're able to deliver critical CSS without incurring the performance impact of an additional request.

Note

WOFF 2.0 font files are supported by the vast majority of active browsers, but will not work with Internet Explorer. If you need to support IE11, access the Google Fonts stylesheet from IE 11 for the appropriate CSS.

You may notice one other change, too: fewer languages in the Google Fonts CSS. By removing rules for unused languages, this inlined CSS weights in under 1kb, compared with the 3.9kb for the CSS file with the rules for Cyrillic, Vietnamese, and Latin Extended.

These changes meet two of Google's recommendations for improving CRP:

  • Minimizing the number of critical resources
  • Optimizing the number of critical bytes to reduce the download time

Performance comparison

Now that we've eliminated an extra request by inlining the Google Fonts CSS, let's take a look at how much this improves page load performance.

Info

For this comparison, instead of using a Lighthouse performance audit with throttling, I'm using Chromium's network tab with "Fast 3G" throttling. The performance improvements weren't obvious in Lighthouse's performance audit because it rounds to the nearest 0.1 s.

Page load with default Google Fonts stylesheet

Network developer tools for default Google Font loading

This page load took 1.79s with 17.5 KB of data, 14.5 KB transferred. The "Load" time here is a little misleading because the page is showing as loading before the font file has loaded.

Page load with inlined Google Fonts stylesheet

Network developer tools for inlined Google Font loading

This page load took 1.22 s with 14.6 KB of data, 14.8 KB transferred.

Here, the inlined page load is 0.57 s faster than the page loading Google Fonts the default way, or a little over 30%. While the stylesheet containing the @font-face rules is small, every HTTP request has overhead. Eliminating an extra HTTP request means elminating that overhead, which means a faster page load.

Update: Viable alternatives

google-webfonts-helper makes it easy to download and self-host Google Fonts. It generates the stylesheet necessary to load custom font files from your web server along with the font files themselves.

Font Squirrel also offers a Webfont Generator that enables you to upload a local font file and convert it to a webfont.

Conclusion

If your website uses Google Fonts and you're loading those fonts the default way, you may want to consider a more performant option.

Inlining the CSS returned from the Google Fonts stylesheet reduces the number of critical resources to load a web font from two to one, but it comes at the cost of not having auto-updating font files. Removing unused languages from the stylesheet reduces the number of bytes needed to load the page, too, helping it load even faster—if only a little.

Self-hosting Google Fonts, with a hand from tools like google-webfonts-helper, is a bit more work to set up and maintain, but it eliminates third-party requests to Google Fonts.

With a bit of work ahead of time, you can use custom fonts and keep page load times in line with even the highest expectations.