How to preload Google Fonts using resource hints

TLDR: you can’t.

According to Addy Osmani, Google’s advice is for you to host your own web fonts. Why? Because Google fonts are updated pretty frequently, and can expire at any time.

That said, if you really want to, you can preload Google fonts. Read on to find out how.

Why preload fonts?

I had a website which used a couple of Google Fonts, which I used to pull in like this:

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Quicksand|Lato">

I attended DeltaVConf a couple of weeks ago, and there was a lot of talk about preloading fonts to improve web performance. Without preloading fonts, the browser fetches HTML – which in turn downloads CSS – and then parses the CSS, and only much later do the associated font files get downloaded when it looks like they’re going to be applied to elements in the DOM.

With preload, the fonts are fetched much earlier on (before CSS is parsed), saving significant time on the first render (as much as a second in a lot of cases).

It looked like a quick win that I could apply to my Google web fonts.

A Google Font link is a stylesheet link

Now, I’ve seen a lot of articles showing how to preload fonts using link rel="preload", but they all provide examples for local font files rather than Google fonts. Their examples look nice and easy:

<link rel="preload" href="/assets/myfont.woff" as="font">

So, I tried to apply this to my Google font:

<link rel="preload" href="https://fonts.googleapis.com/css?family=Quicksand|Lato" as="font">

Changing stylesheet to preload broke everything – I could no longer see my font being used on the page.

I checked my console logs and saw this error, a few seconds after page load:

Error message saying studentmunch.com/:1 The resource https://fonts.googleapis.com/css?family=Quicksand|Lato was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it Please make sure it has an appropriate `as` value and it is preloaded intentionally.

That’s when I realised this isn’t actually a font file I’m pulling in, but a CSS file. So I tried changing the type:

<link rel="preload" href="https://fonts.googleapis.com/css?family=Quicksand|Lato" as="style">

Oddly, my fonts were still not being applied to my document, even though I could see the request being made to Google (this time with the correct Type: ‘style’).

Network tab showing download of Google font

It turns out preload serves as a hint to the browser to download the asset as soon as possible, as it will be needed later. But it doesn’t know when you’re going to need that asset – it’s just believing you when you’ll say you need it. For example, you may load that stylesheet in the head, or you may dynamically load the stylesheet using JavaScript.

Whilst preload downloads the asset, it doesn’t actually apply it, because it shouldn’t until you tell it to.

I therefore had to add my original stylesheet call back in:

<link rel="preload" href="https://fonts.googleapis.com/css?family=Quicksand|Lato" as="style">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Quicksand|Lato">

This pre-fetches my stylesheet, and then immediately requests the stylesheet for applying as CSS. I now have my fonts again – woohoo!

But really, this has done nothing to boost the performance of my page – I’m not downloading the CSS any quicker than before, and the fonts themselves are still taking a while to download. This is because the fonts are external requests made by my call to googleapis.com. I can pre-load the googleapis.com stylesheet but that’s no guarantee it’ll download the fonts any quicker.

What I actually need to do is go and preload the font files, not the Google stylesheet. This is where things get a little messy.

Let’s look at third-party code!

I need to manually preload the external fonts that the Google stylesheet will download.

And to do that, I need to dig into the Google CSS file to see which fonts are needed.

I visited my font in the browser and saw a few @font-face declarations:

Screenshot of Google CSS file contents, declaring multiple font sources

This lists five different font files. I don’t need all these – I just need the latin font. I don’t use Vietnamese on my site.

I don’t need latin-ext fonts either: this stands for Latin Extended, and whilst Latin caters for Western European languages, Latin Extended supports Eastern European characters, for example Ã…, Ä, or Ö. I never need to use these on my site, so I’ll only preload the Latin character set from both fonts.

<link rel="preload" href="https://fonts.gstatic.com/s/quicksand/v7/6xKtdSZaM9iE8KbpRA_hK1QNYuDyPw.woff2" as="font" crossorigin>
<link rel="preload" href="https://fonts.gstatic.com/s/lato/v14/S6uyw4BMUTPHjx4wXiWtFCc.woff2" as="font" crossorigin>

Notice the crossorigin attribute, which is required to preload assets that exist on another domain.

Another way of figuring out which font files you need is to check your Network tab. This has the handy advantage of explicitly showing you how much bloat you’re adding to your page in KB!

Screenshot of network tab showing which fonts are downloaded

Preconnect

If you’re making a few round trips to a CDN to download assets, you can shave a few milliseconds off those requests by opening up a preconnection to the server. From w3.org, the preconnect resource hint initiates an early connection of DNS lookup, TCP handshake and optional TLS negotiation, saving subsequent requests from having to repeat those steps.

I added this resource hint just above my font requests:

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>

Do we need a stylesheet?

We’ve optimised to the point of opening early requests to CDNs, digging into third-party CSS, cherry-picking external assets and then pre-fetching those dependencies manually. So, do we really need that original external stylesheet anymore?

We’ll still need to declare those @font-face styles somewhere on our site, but we can now choose to do this inline or in our own internal pre-fetched stylesheet, saving a round-trip HTTP request to the Google Fonts stylesheet.

I accomplish this by putting the @font-face declarations inline immediately below my preload resource hint:

<style>
@font-face {
 font-family: 'Lato';
 font-style: normal;
 font-weight: 400;
 src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v14/S6uyw4BMUTPHjx4wXiWtFCc.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: 'Quicksand';
 font-style: normal;
 font-weight: 400;
 src: local('Quicksand Regular'), local('Quicksand-Regular'), url(https://fonts.gstatic.com/s/quicksand/v7/6xKtdSZaM9iE8KbpRA_hK1QNYuDyPw.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>

We could stop there, at our own risk

This was my font preload code at this point, where I preload fonts directly from Google’s CDN:

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
<link rel="preload" href="https://fonts.gstatic.com/s/quicksand/v7/6xKtdSZaM9iE8KbpRA_hK1QNYuDyPw.woff2" as="font" crossorigin>
<link rel="preload" href="https://fonts.gstatic.com/s/lato/v14/S6uyw4BMUTPHjx4wXiWtFCc.woff2" as="font" crossorigin>
<style>
@font-face {
 font-family: 'Lato';
 font-style: normal;
 font-weight: 400;
 src: local('Lato Regular'), local('Lato-Regular'), url(https://fonts.gstatic.com/s/lato/v14/S6uyw4BMUTPHjx4wXiWtFCc.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: 'Quicksand';
 font-style: normal;
 font-weight: 400;
 src: local('Quicksand Regular'), local('Quicksand-Regular'), url(https://fonts.gstatic.com/s/quicksand/v7/6xKtdSZaM9iE8KbpRA_hK1QNYuDyPw.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>

As stated earlier, this is risky because fonts are regularly updated by Google and there is no guarantee that older fonts won’t be expired at some point in the future, killing performance on your site with failed requests while users only see your fallback fonts.

I elect to download the fonts myself later and preload locally hosted fonts, but for now let’s do some benchmarking.

Is it any faster?

It’s hard to tell, but I think – think – my site is up to 19% faster at rendering.

On a simulated slow 3G connection, my site originally had a First Meaningful paint of ~12.4 seconds. After preloading the fonts, I got this down to ~10.4 seconds.

I was pretty happy at this point, but then discovered Addy’s talk on YouTube and decided it was probably best that I make a local copy of font files rather than continue to use Google fonts at high risk of breaking.

Here is my final code

After downloading local copies of the fonts, this is my final code (I’m just showing ‘Quicksand’, for brevity):

<link rel="preload" href="https://studentmunch.com/wp-content/themes/studentmunch/fonts/quicksand.woff2" as="font" crossorigin>
<style>
@font-face {
 font-family: 'Quicksand';
 font-style: normal;
 font-weight: 400;
 src: local('Quicksand Regular'), local('Quicksand-Regular'), url(https://studentmunch.com/wp-content/themes/studentmunch/fonts/quicksand.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>

Notice that the crossorigin attribute is still there even though it’s hosted on the same domain as my site. This is needed because fetching fonts has weird CORS intricacies you can read about if you’re interested.

Was it worthwhile?

Of course! Apart from the almost 20% improved rendering time, I feel more in control over the assets of my site and more informed as to which fonts are used where, how big they are, and when they should be loading. It was a useful chance to review my practices.

For example, I realised that at first, I was pulling in these fonts:

https://fonts.googleapis.com/css?family=Quicksand|Lato:400,300,300italic,400italic,700,700italic

This defines font-faces for multiple font-weights and italic style – even though I only use the ‘normal’ style of font.

Whilst these extra font faces aren’t downloaded unless your CSS depends upon it, the CSS file itself is a little larger, at 5.7KB rather than 1.9KB – so even without the preload optimisation, this exercise was worth doing!

And of course, I removed the dependency on the Google font CDN altogether, so that 1.9KB of CSS is now just 728 bytes of inline CSS (minified), with just the latin fonts downloaded.

Loading...