Improving website performance by loading fonts the smart way


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; /* Check for other options. */
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 400;
  src: url('/fonts/merriweather-v30-latin-ext-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */

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:


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.

@import url(',wght@0,400;0,700;1,400;1,700&family=Roboto:ital,wght@0,400;0,700;1,400&display=swap');

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

No local fonts by default

Having a code from google webfonts helper we need to do a little tweak to tell that there are no local fonts available at all, at the start, so the text needs to be displayed once the font is loaded and available.

We are modifying this part of the code:

    src: url('/fonts/merriweather-v30-latin-ext-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */

Into this, by adding local(''),

    src: local(''),
         url('/fonts/merriweather-v30-latin-ext_latin-700.woff2') format('woff2');

This will help us to deal with unwanted CLS when using custom fonts on our website.

By looking into Google Fonts code, this small but really important hack doing a big difference in Core Web Vital (especially CLS).

Sometimes I feel, that Google is presenting their services that work opposite to what they preach. Google Fonts, Google Ads… they simply killing your website CLS metric (a Googles’ PageSpeed metric) when used as provided by… Google (crazy!).

Text rendering

CSS property text-rendering is another thing that will help us tweak the fonts on our website.

The text-rendering CSS property provides information to the rendering engine about what to optimize for when rendering text.

From the options available I like to use, and recommend, optimizeLegibility.

`optimizeLegibility`` The browser emphasizes legibility over rendering speed and geometric precision. This enables kerning and optional ligatures.

Some fonts may look nice but can be hard to read when letters are blended, this will help a bit with that.

Here is an example from of how this will work.

optimizeLegibility Example

Legibility means “the quality of being clear enough to read”.

This parameter provides clarity of the text making reading of custom fonts easier for us, the actual users.

Our additional code will look as follow:

text-rendering: optimizeLegibility;

In such a way, our code in our CSS file will look as follows:

/* merriweather-regular - latin-ext */
@font-face {
  font-display: swap; /* Check for other options. */
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 400;
  src: local(''),
         url('/fonts/merriweather-v30-latin-ext_latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
  text-rendering: optimizeLegibility;

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="{{.Site.BaseURL}}fonts/merriweather-v30-latin-ext-regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{{.Site.BaseURL}}fonts/merriweather-v30-latin-ext-italic.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{{.Site.BaseURL}}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.

  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.