Skip to main content

Improving website performance by loading fonts the smart way

Contents

My site, until recently, was using a system font stack, mostly because I would like to have the lightest website possible.

My featured images, heavily optimized, still account for large LCP (Largest Contentful Paint) and adding custom fonts, in the most common way, can increase the overall weight of the website.

I am happy with my site, but on others, I need to use specified fonts to get the right visual experience across all user devices. In that case, I need to load additional fonts to accomplish that.

The fonts not only add weight to the website but also can hurt CLS (Cumulative Layout Shift) if loaded incorrectly. As CLS is part of essential Core Web Vitals, its poor score can drop a website significantly in Google Search results.

Ironically, Google, on their Google Fonts website provides information (code) on how to implement the desired font in our website. The problem is, that their solution will have a huge negative impact on your website.

Not only they are loaded from an external source, but the speed of loading of your website and external fonts can also vary and cause, ironically, CLS, that Google will penalise you on.

A widely recommended method is to self-host your fonts (even these from Google). This, in most cases, may improve an impact on CLS, but not always. There are plenty of factors in how these fonts are delivered. If our hosting is poor and we are not using CDN (Content Delivery Network) then we can see worse results than loading fonts directly as Google advised.

Self-hosting fonts are the right approach, but it requires a couple of tweaks before it will work well for us in matters of web performance.

Let’s start with, how to load them correctly.

Self-Hosted fonts

Your website will require a font in WOFF2 format (The Web Open Font Format, a compressed file format created specifically for web fonts). With the current state of web browsers, you can forget about WOFF support (and any other formats). WOFF2 is the format that is used by all modern browsers currently on the market.

If you fancy Google Fonts then you will be happy to head to google webfonts helper to find your desired font and download the required files, along with the code, showing you how to implement them on your website.

This website is a real golden nugget. We will use files provided by them, but will not use their code, at least not in full. Additionally, we will do some tweaks to work better for us.

Let’s start with the most popular serif font ‌Merriweather.

Because I write in Polish as well, by default, I am selecting charsets latin-ext (only) and styles as it suits us.

Default styles that I would recomment, which are most common used on websites are regular (400), italic (400i), 700 (bod) and, less commonly used, 700italic (bold italic).

In the third point, we will get our CSS code, for Modern Browsers, to copy. Before we copy it, we will customize the folder prefix as it suits us (in my example it’s just /fonts/.

I will show here only an example for regular (400).

/* merriweather-regular - latin-ext */
@font-face {
  font-display: swap; 
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 400;
  src: url('/fonts/merriweather-v30-latin-ext-regular.woff2') format('woff2');
}

The code needs to be pasted into our CSS-style file.

As the last point, we will download our files.

Once downloaded and unpacked we will have something like that:

/fonts/merriweather-v30-latin-ext-700.woff2
/fonts/merriweather-v30-latin-ext-700italic.woff2
/fonts/merriweather-v30-latin-ext-italic.woff2
/fonts/merriweather-v30-latin-ext-regular.woff2

The Tweaks

Before we will go a little further, let’s see what the important difference is between this code and one provided by Google Fonts for the same font.

<style>
@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;1,400;1,700&family=Roboto:ital,wght@0,400;0,700;1,400&display=swap');
</style>

When you put Google Fonts code into your style.css it will import the following (showing only a part):

/* latin */
@font-face {
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/merriweather/v30/merriweather-v30-latin-ext-regular.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;
}

Google is importing all other charsets that you may not necessarily need!

If you will use this code before your font will load and swap the style on your website (font-display: swap;), the text will be presented with the locally available font (can be split second).

Depending on your internet speed, the time between displaying the local alternative font (Merriweather is not available by default in any operating system) and Merriweather will likely cause a move on your website. This move will account for CLS and hurt your website.

Text rendering

To ensure your text looks crisp and readable, you can use the font-variant-ligatures property. While some older guides suggest the non-standard text-rendering: optimizeLegibility, it can often cause performance issues on long pages.

Using standard CSS properties is a safer and more performant way to achieve the same goal:

body {
  /* Enables standard ligatures (like 'fi', 'fl') */
  font-variant-ligatures: common-ligatures;
  /* Ensures proper spacing between characters */
  font-kerning: normal;
  /* Standard way to help with font smoothing on macOS/iOS */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

Eliminating CLS with Font Metric Overrides

One of the biggest issues with font-display: swap is that the custom font often has different dimensions than the fallback system font, causing a layout shift. Modern CSS allows us to use size-adjust to match the fallback font’s size to our custom font.

Tools like the Capsize framework or Fontaine can help you calculate these exact values.

Variable Fonts: The Future

If your chosen font is available as a Variable Font, you should use it. Instead of loading multiple files for different weights, you load one file that can dynamically render any weight from 100 to 900. This significantly reduces HTTP requests and total payload.


In such a way, our code in our CSS file will look as follows (using Merriweather as an example):

/* merriweather-regular - latin-ext */
@font-face {
  font-display: swap; 
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 400;
  src: url('/fonts/merriweather-v30-latin-ext_latin-regular.woff2') format('woff2');
}

Preloading fonts

Adding fonts to our website through CSS is not enough for web page speed purposes. Some fonts need to be downloaded before the content is displayed. To do that we need to preload the font at the start.

From the four styles we selected (400, 400i, 700 and 700i), we will preload only this that are most likely been used, which are regular, italic and bold (400, 400i, 700).

We need to paste the below code into our <head> just after the CSS file is loaded.

<link rel="preload" href="{{ absURL "" }}fonts/merriweather-v30-latin-ext-regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{{ absURL "" }}fonts/merriweather-v30-latin-ext-italic.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{{ absURL "" }}fonts/merriweather-v30-latin-ext-700.woff2" as="font" type="font/woff2" crossorigin>

Cache the font

Depending on the environment where our website is served, it’s good to specify the right approach for caching fonts. This is mostly done through HTTP headers.

My website is hosted on Netlify, hence I will do that by specifying custom headers in the _headers file located in my static folder, which will be placed in the root folder when my site will be built using Hugo.

Specific to fonts, I am adding the following header that will help us deal with reported errors by PageSpeed Insight about serving static files through a sufficient caching policy.

/*.woff2
  Cache-Control: public, max-age=31536000, immutable

If we follow everything mentioned above, we shall see a huge improvement when running our website through PageSpeed Insight, especially with CSL.

That’s however depend, how fast is our hosting and if we using CDN or not.

PageSpeed Metrics example

Just recently, when I decided to add to my website, where I used to use system font stack a Roboto font as a first, with all the above teaks my PageSpeed still reported a score of 100.

Share on Threads
Share on Bluesky
Share on Linkedin
Share via WhatsApp
Share via Email

Comments & Reactions

Categories