Optimizing Web Fonts in Next.js 13
December 7, 2022
Web fonts are an essential aspect of modern web design. They allow for beautiful typography on the web, unique to your product✨
However, using web fonts can also introduce challenges. If you're a frequent internet user like most people reading this, chances are that you've come cross the following phenomenon:
You navigate to a website that initially displays a default font, such as Times New Roman
or Arial
, until the web font has been downloaded and installed. It then quickly switches the font from the default to the web font, often causing layout shift.
The layout shift can range from just moving a couple of pixels to a major shift caused by large fonts, or by different line break positions.
This phenomenon is called Flash Of Unstyled Text (FOUT). If the web font takes a long time to download, the user may see the text on the page "flash" or change from the default font to the web font.
In other cases, you may not see any text until the web font has been downloaded
This is called Flash Of Invisible Text (FOIT), in which case the browser draws an invisible fallback font. This happens when the font-display
CSS property is set to block
.
I think we can all agree that both FOUT and FOIT are... distracting and confusing. However, it's challenging to avoid FOUT and FOIT entirely.
One of the main challenges with using web fonts is that the browser must download the typeface before it can be rendered and displayed on the page. This can take time, especially if the user has a slow internet connection or if the web font is large.
Luckily, there are some optimizations we can implement to make working with web fonts a little less painful!
Optimizations
Before we cover the optimizations, let's first take a look at the most basic, unoptimized approach to understand the issues we're dealing with.
In the following examples, we will be showing the Next.js implementation when using web fonts. However, it is important to note that these examples and techniques are not specific to Next.js and can also be applied when using other frameworks or just HTML.
HTTP Connections
To add a web font to a Next.js project, you can use a <link>
tag in the <head>
of the _document.js
file. In the_document.js
file, you can modify the Document
component and pass the font stylesheet as children to the Head
component.
When we load a page using the above-mentioned code as the content of
_document.js
, the browser goes through several steps to make sure the web font is properly loaded and displayed on the page.
The browser starts by reading the initial HTML and encounters the
<link>
tag, which it identifies as an external stylesheet.The HTML parser then checks the
rel
attribute to figure out what type of resource the link is referring to. In this case, it's a CSS stylesheet.Next, the HTML parser sends an HTTP request to the server at the specified URL,
fonts.googleapis.com
, and receives a response containing the CSS rules and styles for the web font, in this case looking like the following:
For readability, I only included the latin
subset here. In reality, more subsets are included in the response.
After the stylesheet is downloaded synchronously, the browser constructs the rendering tree using the DOM and CSSOM. This initial rendering tree shows the fallback font to the user.
Once the rendering tree is fully constructed and the first paint has occurred, the browser sends a request to
fonts.gstatic.com
to download the actual font file specified in the@font-face
'ssrc
property.Finally, the browser uses the font file to render the font on the page, allowing the web font to be displayed correctly.
If we take a closer look at both the stylesheet and font request, you'll see that it's not just about downloading the resource. Because we're contacting other servers, the browser first has to set up a connection using DNS lookups, TLS negotiations, and TCP handshakes.
Establishing a connection can take quite a bit of the total request time. To avoid this at request time, we can add two <link>
tags using the preconnect
browser hint for both https://fonts.googleapis.com
and https://fonts.gstatic.com.
This tag will instruct the browser to establish a connection to the server where the resource is hosted as soon as the page begins loading, which can reduce the time it takes for the request to be sent and the response to be received.
The preconnect
browser hint can be a huge help when it comes to speeding up request time and avoiding FOIT and FOUT when loading web fonts on your website. By using this technique, you can establish a connection to the server where the font is hosted as soon as the page begins loading, which can make a big difference in how quickly the font is downloaded and displayed.
Font-face descriptors
We just saw how we could reduce the time it takes to fetch the web font, however we still haven't got around the layout shift yet.
When working with fonts, there several different measurements that are used to describe the characteristics of a font. Some of the most important measurements within a font include:
Ascender: The line that defines the tops of the ascenders on certain letters, such as
b
,d
,f
,h
,k
andl
; parts of the letters that extend above the x-height.Meanline: The line that defines the tops of the lowercase letters with bowl shapes, such as
o
andd
.Baseline: The line on which most letters sit, used as a reference point for positioning the letters in a line of text.
X-Height: The height defining the difference between the meanline and baseline. This property helps determine the size and spacing of the letters.
Descender: The line that defines teh bottoms of the descenders on certain letters, such as
g
,j
,p
,q
, andy
; parts of the letters that extend below the baseline.
Since fonts have different measurements, we can end up with layout shift even if the fallback font and the web font have the same font-size
.
The pink line represents the x-height
, meaning the difference between the meanline and baseline.
For this reason, we need more control over the size of the rendered font besides its font-size
to reduce the layout shift. One approach is by using the size-adjust
@font-face
descriptor.
size-adjust
The size-adjust
font-face descriptor scales all metrics associated with the font by a specified percentage. By adjusting the size to match the web font, the size of the text will remain (roughly) the same even if the font is swapped from the fallback to the web font, which can help prevent unexpected changes in the layout of the page.
Let's say you want to use the Poppins web font on your website, but you want to use the Baskerville font as a fallback in case the web font doesn't load. In this case, you can use the size-adjust
property to make sure the size of the text remains consistent, no matter which font is being used. This means you could either adjust the size of the Poppins font to match the Baskerville font, or vice versa.
Since the font-size
is set in the CSS for the same element, we can't use this property to change the size of the falback font, as it's not possible to differentiate the size for the fallback font or the web font. However, with the size-adjust
property, we can set the size of the font in the font-face
property, which applies the adjusted measurements to all text using that font.
We avoid most to all layout shift, since the fallback font's measurements now closely match the web font.
While adjusting the size-adjust
property can help prevent layout shift when using web fonts, it isn't always sufficient.
If you want even more control over the rendered font, you can use other CSS descriptors such as ascent-override
, descent-override
, and line-gap-override
to define the ascent metric (height above the meanline), descent metric (height below the baseline), and line height, respectively. This can help ensure that the fallback font closely matches the web font's layout and appearance.
We've been using a serif fallback font even when the web font is sans-serif, but you can get better results by choosing a fallback font that matches the type of font you're using. For example, if you're using a sans-serif web font, you should choose a sans-serif fallback font such as Arial. This can help ensure that the font measurements are as similar as possible.
next/font
module
Addingpreconnect
and font-face
descriptors can improve the performance of fonts on your website by speeding up their loading time and defining their characteristics. However, manually adding these optimizations can be tedious and prone to errors. Ideally, we want our fonts to be optimized automatically.
This is where the next/font
module comes in. This module was introduced in Next.js 13 and allows you to easily add local and Google fonts to your website without worrying about the details of optimization.
The next/font
module makes it much easier to work with local and web fonts by automatically:
Downloading the web font at build time and serving it locally
Adding a fallback font and automatically adjusting its measurements to resemble the chosen web font as closely as possible.
You can use it by importing a font from the next/font
module and using the font instance to apply it to specific components in your website.
Self-Hosting Fonts
At build time, the next/font
module downloads both the font stylesheet from fonts.googleapis.com
, and the font files defined in the @font-face
src
's that was previously fetched.
The fonts are now available locally, without requiring any external requests.
In January 2022, the German Court ruled that using Google Fonts violates GDPR regulations. The next/font
module helps improve privacy by allowing web fonts to be self-hosted.
Automatic Matching Fallback Font
The next/font
module not only lets you serve fonts from your own domain, but it also automatically provides a custom fallback font that closely matches the intended web font, and even calculates the necessary measurements such as size-adjust
, ascent-override
, descent-override
, and line-gap-override
to make the fallback font closely match the web font.
The fallback font values are calculated automatically at build time
When using the next/font
module to display text on your website, you'll likely experience little to no layout shift when the font is changed.
Preloading
The next/font
module also automatically preloads fonts when a subset
has been defined. This can help both performance and user experience.
A preloaded font is downloaded as soon as possible, typically before the browser starts rendering the page. This avoids the potential for the font to render incorrectly or change while the page is loading, creating a seamless experience.