DevelopmentGatsbyJS | 10 Min Read
Optimising Google Fonts for Increased Performance on GatsbyJS
GatsbyJS makes blazing fasts websites already but often importing fonts can be an issue, here's how to optimise Google Fonts for increased performance.
The Problem
Google Fonts is awesome, it has loads of great free fonts for anyone to use pretty much however they wish. But, even with all of this goodness there is one issue we can't escape, Google Fonts can be incredibly taxing on your website's load time.
Sometimes, we can measure the impact of Google Fonts loading in seconds rather than milliseconds which in a world where your entire site and business can be penalised for slow load times, this is a major issue.
Recently when redesigning my portfolio site, I spent a good few days trying to come up with an adequate solution for this issue and it is that solution which is what I'm going to share with you today.
How to not import Google Fonts
First, let's take a look at how we should not be importing Google Fonts.
1<style>2 @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");3</style>
htmlGoogle lists using '@import' as one of the main ways of importing their fonts onto your site and while it is very user friendly, it comes with some performance issues for your website. To show these issues, let's take a look at some statistics for my own site:
Device | @import | My Solution |
---|---|---|
Desktop - no throttle | 3.5 | 2.6 |
Mobile - throttled | 3.2 | 2.7 |
So, as you can see the '@import' method while great for convenience and user friendliness isn't the best when it comes to performance. So, let's now look at some other methods for integrating Google Fonts onto a GatsbyJS site.
Possible Solutions
Here are some possible solutions that I experimented with before arriving at my current solution.
gatsby-plugin-google-fonts
First, we have the Google Fonts plugin for GatsbyJS, you can find the plugin documentation page here.
What this plugin allows you to do is define the fonts you want to use on your site in your 'gatsby-config.js' file and then when you build your website it will download the fonts and serve them to you from the server along with your site.
This plugin is a step in the right direction by not downloading the fonts every time we load the site but it increases the build time of the site along with the total bundle size.
As a rule of thumb, we want to try keep the amount of external packages to a minimum as this increases both our page load time and bundle size.
So, while this plugin was a good option, it doesn't quite make the cut.
Typefaces
Now, this is an interesting option available to us, developed by Kyle Mathews (the guy behind GatsbyJS) it aims to solve some of the problems other methods of importing fonts gives us like the plugin above but while addressing some of the issues the above plugin gives us.
So, to use this plugin we need to find a typeface npm package for the font we want to use. For our earlier example of importing 'Roboto', we would use the package:
1typeface-roboto
Once, we know the package we want to use we just need to install this package to our project and then require the files in our project which for gatsby can be done in the 'gatsby-browser.js' file.
To install the package use:
1npm i typeface-roboto
Then just require it in your 'gatsby-browser.js' file by using:
1require('typeface-roboto')
Now, this package removes the need to download the files each time we build the site, cutting down on our total build time. It also keeps the benefits of hosting the files locally and serving them alongside our website files decreasing the total time our users need to wait for our site to load.
But, there is still the issue of it being an external package being called in our code. So, we won't be using this method either.
But, what if we could create the benefits of both of these packages without needing to use either them?
Essentially, could we host our fonts locally on our site, serve them with our site without having to download them each time while not needing an external package to do so.
The answer is YES! Obviously... Otherwise, I wouldn't be writing this.
What I did
You see when we use the '@import' method from earlier all it is doing is calling a Google API to get a list of all of the files it needs to download via their respective URLs. We can see this in action if we navigate to the URL we are calling the '@import' on. For example, the URL imported earlier returns:
Now we know why using '@import' can have such an impact on our page load times because not only do we need to wait for an API to reply with the relevant URLs, we also need to wait for the users browser to download each of these files. So, even though on Google Fonts' website we are only selecting 3 fonts, in reality we are downloading 21 files when the page loads and this takes up a lot of precious time.
Lucky for us there is a way round this.
You see in this list of files we are actually calling multiple of the same font / font weight but with different language support. So, depending on the languages of your audience you may or may not need to support these extra characters. For me, I know the overwhelming majority of my audience can be supported by using the 'Latin' typeface but this may not be the case for you.
Downloading the Fonts
Now we know what language support we need, we're going to download just the files we need by accessing the URL contained within each of the font-face objects we require. For our example, we would need to use the following 3 font-faces and the urls contained within them:
1/* latin */2@font-face {3 font-family: 'Roboto';4 font-style: normal;5 font-weight: 400;6 font-display: swap;7 src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2');8 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;9}10
11/* latin */12@font-face {13 font-family: 'Roboto';14 font-style: normal;15 font-weight: 500;16 font-display: swap;17 src: local('Roboto Medium'), local('Roboto-Medium'), url(https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmEU9fBBc4.woff2) format('woff2');18 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;19}20
21/* latin */22@font-face {23 font-family: 'Roboto';24 font-style: normal;25 font-weight: 700;26 font-display: swap;27 src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfBBc4.woff2) format('woff2');28 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;29}
jsx{7,17,27}When we visit each of those urls, we are prompted to download a font file that relates to the font-face object for that url. When downloading these files, I would recommend renaming them to something a bit more user-friendly like 'fontFamily-fontWeight'.
Importing to our Gatsby Site.
To import our fonts into our GatsbyJS project, we need to move the files into our project files, and check them into our git repository so they are committed with the rest of our project and sent to the build server when we deploy. The files can be in any location of your choosing but I choose to put my files in the following location:
1/2 /src3 /assets4 /fonts5 ...files here
Once you have moved your files and checked them into your repository we can move onto using our new fonts.
Using our Fonts
To use our fonts in our styling, we need to create the relevant font-faces. This can either be done inside a global css file using vanilla CSS or if you're using Styled Components you can call the 'createGlobalStyle' API exported from Styled Components which allows us to create global styles from inside a component. Personally, I used Styled Components on my site but I will cover both below.
Styled Components
04/06/2020 EDIT: Following this Styled Components method I have been experiencing some errors in relation to the loading of fonts and the browser rejecting them with a 404. I resolved this issue by using the CSS method I document below this section. If you have a recommendation on how to get the below method working I would love to hear it. Therefore, I am going to leave the below method up but I recommend following the global CSS method below.
If you're using Styled Components you can use the same code I have and just change it to suit your needs. To do this, we need to create a new Fonts component in the same location as your other components. Once you have created this file, drop the below code into it:
1import { createGlobalStyle } from "styled-components";2
3const RobotoRegular = "src/assets/fonts/Roboto-Regular.woff2";4const RobotoMedium = "src/assets/fonts/Roboto-Medium.woff2";5const RobotoBold = "src/assets/fonts/Roboto-Bold.woff2";6
7const Fonts = createGlobalStyle`8 @font-face {9 font-family: 'Roboto';10 src: url(${RobotoRegular}) format('woff2');11 font-weight: 400;12 font-style: normal;13 }14 @font-face {15 font-family: 'Roboto';16 src: url(${RobotoMedium}) format('woff2');17 font-weight: 500;18 font-style: normal;19 }20 @font-face {21 font-family: 'Roboto';22 src: url(${RobotoBold}) format('woff2');23 font-weight: 700;24 font-style: normal;25 }26`;27
28export default Fonts;
jsxNow, you just need to change the configuration of this component to match all of the fonts you need installed, you need to have one '@font-face' object per font per font weight.
Once you have imported all of your fonts and created a font face for all of them we then need to go to your layout.js file (or, whatever your template file is) and import the fonts component and return it to the page.
To import them to your layout.js file use:
1import Fonts from "./Fonts";
jsxThen just include a reference to this component inside your returned component like so:
1const Layout () => (2 <>3 <Fonts/>4 </>5)
jsxNow, with all of this set-up anywhere we want to use one of these fonts in our styling we can do so with:
1p {2 font-family: 'Roboto', sans-serif;3 font-weight 400;4}
cssVanilla CSS
Creating the font faces for our fonts using vanilla CSS is largely the same as with Styled Components minus the external component parts, instead we will be putting all of the information inside a global CSS file. For our example we need to include the below in our global css file:
1@font-face {2 font-family: "Roboto";3 src: url(src/assets/fonts/Roboto-Regular.woff2) format("woff2");4 font-weight: 400;5 font-style: normal;6}7@font-face {8 font-family: "Roboto";9 src: url(src/assets/fonts/Roboto-Medium.woff2) format("woff2");10 font-weight: 500;11 font-style: normal;12}13@font-face {14 font-family: "Roboto";15 src: url(src/assets/fonts/Roboto-Bold.woff2) format("woff2");16 font-weight: 700;17 font-style: normal;18}
cssWith these imported into our css, we just need to import our global css file in our gatsby-browser.js file for our site to use like so:
1import "./src/styles/global.css";
jsxThen we're done, you should now have access to all of your new fonts on your site using:
1p {2 font-family: 'Roboto', sans-serif;3 font-weight 400;4}
cssConclusion
Using this method, we are able to only load the fonts we need while serving them locally from our server; cutting down any external load times. We also don't need to worry about importing any external packages and bloating our bundle size.
By, using this way of importing Google Fonts it has helped me increase the performance of my site greatly as shown in the speed tests at the top of this post, these improvements were also reflected in my Lighthouse audit score so if that is important to you this could help improve your score.
Finally, it's worth mentioning that if you need to support older browsers you may need to source other file types than 'woff2'. However, all modern browsers support this font file type so I will leave the decision up to you. You can find more info on what does and doesn't support this format here.
Honestly, although this method takes more set-up then the external packages I prefer it for the control it gives us along with the performance and bundle size advantages.
I'm curious to know, will you adopt this way of importing Google Fonts on your GatsbyJS site? Or, will you use one of the external packages? Or, something else altogether? Make sure to let me know over on Twitter @MrConerMurphy.