https://www.industrialempathy.com/posts/high-performance-web-font-loading/
Industrial Empathy
About
More than you ever wanted to know about font loading on the web
7 min read. 1 comments*
When I started thinking about writing a post about web font loading
my intention was to propose relatively sophisticated ideas that I've
been playing with for a while. However, as I was trying to use them
in real-world websites I realized that deployment of the more
advanced techniques is de-facto impossible without the creation of
new web standards.
With that the TL;dr of this post is: Use font-display: optional.
However, I and many others really like our custom fonts. See the rest
of the post for how we can get our cake and eat it, too-with a tool
that automatically makes fallback fonts behave like their respective
custom font counterpart.
Web fonts and Core Web Vitals #
2 metrics in Google's Core Web Vitals are directly impacted by font
loading:
1. Largest Contentful Paint (LCP) measures (among other things) when
text renders. With text rendering blocked behind the web font
download, LCP may be delayed.
2. Cumulative Layout Shift (CLS) measures the document shifting
around as the browser loads additional data. A browser switching
from fallback font to a custom font leads to layout shift if
fallback and custom font flow differently.
The following video is showing the layout-shift created by
font-loading.
CLS and invisible text #
Font-based layout shift doesn't require the fallback font ever having
displayed. If the page renders without the custom font having loaded
but the fallback text remains invisible (this happens with
font-display: auto, the default) then the space that is reserved for
the invisible text depends on the space that would be taken up by the
fallback text. Once the custom font comes in and the text becomes
visible, there is a layout shift as the space taken up for text
changes.
Stop worrying and use font-display: optional #
Why font-display: optional is currently the only good option #
With font-display: optional the browser only renders the custom font
if it is available extremely quickly. In most scenarios that requires
it being cached locally.
This leads to the best possible LCP: Your text always renders
quickly, independent of network speed.
And it leads to the best possible CLS: Your custom font loading never
causes layout shift because it is only used when it is available for
the first text paint.
When not to use font-display: optional #
The one reason that makes usage of font-display: optional impossible
is if there is no viable fallback font: You need the custom font to
load to make sense of the content. Generally that is the case for
icon fonts. You probably shouldn't use these in the first place as
they are bad for accessibility: You need to see the icons to
comprehend them, and you cannot assign them alt-text.
Using preload with font-display: optional #
Browsers will only download a font for a web page when CSS evaluation
completes and it is determined that it is actually used on the page.
That is much later than e.g. when image downloads are initiated which
are done by the so-called pre-parser that does a quick scan of the
HTML document as soon as it is available to the browser (and without
blocking on synchronous scripts, stylesheets, etc.).
The common work-around is to use a link-preload element like this
which explicitly instructs the browser to start the font download as
soon as it discovers the element.
The question is: Should you use these together with font-display:
optional? The conventional wisdom is: No. The reason being that with
font-display: optional either your font is already in cache in which
case this doesn't do anything, or the font download is likely not
fast enough to make the short deadline and the browser would render
the fallback font anyway. In the latter case you give bandwidth at
the most urgent time to the font which will never render and that
bandwidth could be used to download other critical resources instead.
With that said: My website does it anyway. The reason is that in my
experience users with very fast connections can actually download the
font in time, and because my website has no other critical resources
there is really nothing the fonts compete with. This is certainly not
true for most websites.
Fonts and CDNs #
One of the big changes in the web ecosystem over the last few years
is that browsers no longer cache resources across top level sites.
That means if your site and my site both load the exact same Roboto
from Google Fonts, the browser will download it twice as opposed to
only once like they used to do. This is very sad. It is, however,
also the right call in the short-term from a privacy & security
perspective. In the long-term, maybe we can define web standards that
eliminate the privacy & security threats from cross-origin caching
for heavily shared resources like fonts.
So, what are the consequences of this change in browser caching
behavior? The main change is that font CDNs like Google Fonts and
Adobe TypeKit now strictly make your site slower. They used to help
with cross-site caching, but that benefit is gone. Instead they add
expensive cross-origin requests (and their DNS lookups, TLS
negotiations, etc.) into the critical path of loading your website.
With that it is clear that we should self-host all fonts on our
primary domain for maximum performance. With fonts this can sometimes
be problematic for licensing reasons, etc. but there is a good middle
ground: Instead of self-hosting the fonts, self-host the loading
code. For all common Font CDNs (even TypeKit with some digging, they
default to JS based loading) this is simply a CSS file. Just download
that CSS file. It won't bite . Or, if your font provider likes to
sometimes change it, just fetch the CSS file once during your build
process. Then inline the CSS file into your HTML and you completely
eliminate the expensive cross-origin request from your critical path.
While this approach still downloads the fonts themselves from the
CDNs this doesn't hurt when you are using our friend font-display:
optional.
What if I really don't want to use font-display: optional #
So, I have a solution for you. It works remarkably well. This is
based on an idea/tool by Monica Dinculescu that she published in
2016. It allows tweaking your fallback font such that it uses
approximately as much space as the custom font.
This is an awesome idea as it avoids the layout shift issues
associated with loading the custom font: With the fallback already
taking up the right amount of space, the custom font just swaps back
into the same space when it loads.
Tool: Perfect-ish font fallbacks #
My contribution over Monica's idea is that I made a tool that
automatically matches the fallback font to the custom font-because
computers are good at that stuff. Try it out here.
The tool allows you to select every Google Font from a select menu.
If you aren't using a Google Fonts, you can remix this Glitch for a
custom solution.
Samples #
The fallback-to-custom-font matching works really well in most cases.
Here the left font is the custom font and on the right side is Arial:
Comparison of rendering of Montserrat and Arial
However, the whole thing is just an approximation. It definitely
happens that things do not match 100%. (The screenshot shows the same
text/font as before, but uses a different viewport width). The
solution works most of the time. It isn't perfect but better than
always having a major layout jump.
Same fonts as in previous image but showing that Arial flows one line
shorter
Finally, your mileage may vary with more extreme fonts. For very
narrow fonts the fallback font may become unreadable. Having said
that, for fonts that are commonly used this is not a problem.
Text with negative letter spacing that has the characters flow into
each other
Deploying fallback corrections to the website #
The output of the tool is a bit of CSS like
Unfortunately, this is where things get complicated. What you need to
do is have your page use this CSS but only (and that is very
important) until the moment that it renders the custom font. You can
try out this demo for a working implementation. This is based on Bram
Stein's excellent FontFaceObserver and the magic is basically here:
new FontFaceObserver("Montserrat").load().then(function () {
var s = document.getElementById("font-correction");
s.parentElement.removeChild(s);
});
What this does is: When our custom font loads (Montserrat), then
remove the style element we defined above such that the correction
for the fallback font is eliminated.
So far, so simple.
Where things get really complicated is when trying to deploy this
with more than one web font (or variant of the same logical font).
Then you need to manage N corrections (letter-spacing and
line-height), apply them only to the correct text that is styled with
that font, and remove them individually as the respective font file
loads.
I've decided for myself that it isn't worth the effort as it would be
too fragile to ever work in practice.
A web standard solution #
While handling the font-loading state machine is complicated in
JavaScript (besides the ridiculousness of using JavaScript to control
font-loading) and expressing the font-changes in CSS relative to the
base styling is even more complicated, there is a party that could
handle this quite easily: The browser. What if I could say: "When
Arial is a fallback font for X, then use the following letterSpacing,
etc.".
In CSS that would look something like this:
@font-face {
font-family: "My font";
src: ...;
/* WARNING: Just a proposal */
/* Configure fallback font for "My Font" */
fallback-font: "Arial"; /* Would also avoid specifying redundant fallbacks in font-family rules */
fallback-font-letter-spacing: 0.0605em;
fallback-font-word-spacing: 0.001em;
fallback-font-line-height: 1.3;
}
And the best part of an approach like this: browsers could just ship
better defaults for common fonts. There aren't that many fonts in
use, and at <=64bit of information needed per font, browsers could
easily ship fallback configuration for the ~1000 most common web font
names.
Summary #
Using font-display: optional together with self-hosting the CSS for
your web fonts gets you in really good shape with respect to LCP and
CLS. There are more sophisticated techniques but it is probably worth
waiting for web standards that make it easier to use them.
Since you've made it this far, sharing this article on your favorite
social media network would be highly appreciated ! For feedback,
please ping me on Twitter.
Comments #
Boris Schapira said: Really interesting yet I can't shake the
feeling that something's wrong with pushing `font-display:
optional`.
To improve CLS of the first page load, brands are being asked to
sacrifice a part of the identity they've been building for years.
Thread
Add a comment Clicking the button will open a new window to compose a
tweet. Tweets with a URL to this article may show up as comments on
this article.
174
Published 29 Jan 2021
Malte Ubl