How to self-host Google fonts for your web application | Part 2/2: Download and provision

They are everywhere. For a good reason as well - typography plays a big role in UI design, and Google provides designers and developers a simple solution by providing popular fonts over their CDN. You might, however, have good reason to serve these from your own server.

In the first part of this series, I've attempted to compare self-hosting Google fonts with having them provided by Google. My personal winner was the former option and this article describes how to implement it.

Recapitulation: DIY or CDN?

In a nutshell: Should you host your own fonts?

  • Are you serving your website to European citizens and don't want all that additional GDPR hassle (user consent, an extensive privacy policy, legal notes, ... you name it)?
  • Do you want more control over your served content? And do you feel confident to adjust some configuration on your server?
  • Would you like to rely less on the big player's infrastructure to serve critical files? Or are you tired of consultants and lawyers telling you to do so?

If you find yourself among these three, you are likely a good candidate.

Download the fonts you would like to self-host

For the following steps, I'll assume you have the following project structure.

| - fonts/
| - styles/
|   | - main.css
| - index.html
  • Save the downloaded file in your project folder
  • Unzip the directory.

You will now have a variety of .ttf files available. Each of these files represents a specific font style

You can achieve even better results using Google fonts helper. It provides you more modern formats with fallback options & CSS snippets.

Add the font-face rules to your CSS

Now that we have the fonts available, we must configure them in our styles. This is done by using the @font-face rule.

@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  src: url('../fonts/Roboto-Regular.ttf');
  font-display: swap;

You can then start applying the font-family as you usually would:

.custom-font {
  font-family: 'Roboto', 'sans-serif'

Next, apply the class custom-font to your HTML. Notice how the style, followed by the font file, is then loaded into the browser:

So far so good, but there are still some improvements to be made here. Google still does some important things for us, some of which are:

  • Providing browser-specific font types
  • Taking care of preloading fonts without blocking the browser
  • Setting the correct headers for cache control

We can, however, set these features up ourselves, so let's go ahead and do that

Get the browser-specific - version of your font

Google's CDN automatically serves you the font format that is supported by your browser. And while the .ttf file from earlier works, .woff formatted fonts are served faster and supported by all modern browsers.  

Using the Google fonts helper app saves you a bit of manual labor here.

Fortunately, there's a simple trick to figure the correct font to download. Let's take Roboto as an example again.

  • Head to the browser you require the fonts for (or use a browser simulator like Browserling)
  • Parse the URL you found on Google fonts and navigate to it
  • You will find a CSS file that has the optimal @font-face CSS rules for your browser

Some examples for other font formats and browsers using them are

After downloading the font and replacing the source URL in the main.css file, everything still looks as expected:

Configure your app to preload fonts

By default, loading CSS happens synchronously and thereby blocks the browser's rendering process. This also includes our @font-face CSS rules. Before rendering text, the browser must first load the necessary fonts. This takes time and is bothersome for users. Luckily, it is also fairly simple to circumvent.

With this approach, textual content is rendered before the fonts are available. Then, once loaded, they're applied - the fonts are swapped.

In your index.html:

  • Instead of requesting the CSS file that deals with fonts with rel="stylesheet", add the rel="preload" and as=font attributes
  • Then, use the window.onload event to overwrite the rel attribute once the HTML is done loading
<!-- Replace -->
<link rel="stylesheet" href="./styles/main.css">

<!-- With -->
<link rel="preload" as="font" href="./styles/main.css" onload="this.onload=null;this.rel='stylesheet'">
This trick alone boosted my lighthouse score by an unbelievable amount of 8%

Configure your server to send a cache header

To demonstrate this, I use Nginx v1.18 on a 5$ Digital Ocean Droplet:

  • SSH into your droplet with ssh root@<ip-adress>
  • Open the default Nginx server config nano /etc/nginx/sites-endabled/default/
  • Add the following wildcard rule right above the first location to apply a cache of 30 days (=2592000 seconds) for static assets
# Set cache headers for all css and font files
# This header can also be extended for other static content
location ~* \.(css|ttf|woff|woff2) {
  add_header Pragma public;
  add_header Cache-control "public max-age=2592000";

# location / { ... }
  • Use systemctl reload nginx to gracefully restart Nginx
  • Move your files to the droplet using Linux' scp or, alternatively, Filezilla
# Copy the whole directory to the droplet 
# Note that I am using /test/ as a path instead of just / 
# as scp recursively copies the whole folder structure
scp -r <path-to-project>/test/ root@<droplet-ip>:/var/www/html/

Once that is done, try and access your droplet by its IP address. You should see something like this:

If you made it here in one piece (especially if you've read the bit about data privacy), you're now the proud owner of a website with its own instance of Google Fonts.

Further reading & resources

There are more excellent projects and articles on the internet that deal with this topic.