Web fonts enable us to have good design, branding, readability and accessibility. Moreover, they would bring selectable, searchable, zoomable and high DPI text. However, with every advantage comes a trade off.
All other parts:
Part 2 use Preload/Prefetch to boost load time
Part 3 JavaScript tips and tricks
Intro
People use web fonts heavily these days without realising how much impact it would have on the overall page speed.
This doesn't mean you shouldn't use them, just the fact that you should know how to use an optimised font and to how to load to reduce the impact on page load.
Optimising web fonts depends on where you host the font, your web application's design and back end server, your technical abilities and how far your company is willing to invest in this. But don't let this hold you from following the techniques I will go through, even a little improvement on overall page load counts towards a better user experience.
Finding out how many fonts you are using
Before you start the task, you should find out what fonts are used throughout your website. You can easily find this out using Audit tabs in Chrome DevTools' network tab and looking at the HTTP requests.
Once in the tab click on the filter icon and look for file extensions like woff
, woff2
, tff
, otf
and so on:
Deciding how many is too many
Research shows on average 67% of web pages use custom fonts, with websites averaging between four web font HTTP requests.
Once you figured out how many fonts you are using, you should think about how many fonts you should use. Normally these decisions are driven by UX or design team, but you can definitely have a word with them.
If you are thinking how many is too many, the rule of thumb is that three is crowd and two is the best to stick to.
If you use less fonts throughout your website, not only the number of network requests are reduced, but also the design is more streamlined and consistent.
Choose the subset you need
Unless you have an international website supporting multiple languages, it doesn't make sense to use all character sets. Choose only what you need and for the languages you use.
Ilya Grigorik a Developer Advocate and Web Perf Guru at Google, explains how you can subset or split web fonts into multiple unicode ranges to deliver only what you need.
You can use the Unicode-range descriptor to specify a list of range values, which can be set out in three different forms:
- Single codepoint: indicates a single character.
- Interval range: indicates start and of the codepoints of a range of characters.
-
Wildcard range:
?
character indicates any hexadecimal digit.
To demonstrate the possibilities, this is how you can split the Font Awesome into Latin and Japanese subsets. Once done, the browser would download each subset as needed.
Note: Unicode-range subsetting is particularly important for Asian languages, where the number of glyphs is much larger than in Western languages and a typical "full" font is often measured in megabytes instead of tens of kilobytes.
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome-l.woff2') format('woff2'),
url('/fonts/awesome-l.woff') format('woff'),
url('/fonts/awesome-l.ttf') format('truetype'),
url('/fonts/awesome-l.eot') format('embedded-opentype');
unicode-range: U+000-5FF; /* Latin glyphs */
}
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome-jp.woff2') format('woff2'),
url('/fonts/awesome-jp.woff') format('woff'),
url('/fonts/awesome-jp.ttf') format('truetype'),
url('/fonts/awesome-jp.eot') format('embedded-opentype');
unicode-range: U+3000-9FFF, U+ff??; /* Japanese glyphs */
}
Note: The local()
directive allows you to reference, load, and use locally installed fonts. The url()
directive allows you to load external fonts, and are allowed to contain an optional format()
hint indicating the format of the font referenced by the provided URL.
There is a catch however which is not all browsers currently support unicode-range. Fortunately most of the major browsers support it, you will need to have a fallback option.
Now if you're asking how I can determine which subsets I need, the answer is:
- If the browser supports unicode-range, then it will automatically select the right subset. You will just need to provide the subset files and specify the appropriate range in the
@font-face
rules. - If the browser does not support this feature, then you will need to hide all unnecessary subsets, which is, you have to specify the required subsets manually.
You can generate the subsets using an open source tool called pyftsubset tool. In addition to that some font services allow manual subsetting via custom query parameters.
There are some other opportunities you can leverage apart from subsetting. Let's say you just need a Google font to show your page title. You can specify which characters to download from that font only using a &text=
syntax:
<link
href="//fonts.googleapis.com/css?family=Roboto&text=log"
rel="stylesheet"
/>
Warning: You need to be careful since this is not a word selector but a character selector. This means characters l
, o
, and g
will be shown on Roboto.
Deliver optimised font formats
There are four major formats used on the web:
- Embedded Open Type (EOT): Microsoft designed this format, which is now only used by IE browsers.
- True Type Font (TTF): A format that’s been around since the late 1980s that has partial IE support.
- Web Open Font Format (WOFF): A format developed in 2009, which is essentially OpenType or TrueType with compression and additional metadata. It enjoys widespread support but is not available in some older browser.
- Web Open Font Format (WOFF2): An improvement on WOFF that provides, on average, a 30% reduction in file size. Support is still a work in progress for many browsers.
There isn't one format that works in all of the browsers, hence the need to deliver multiple formats to provide a consistent experience. From Google developer's website:
- Serve WOFF 2.0 variant to browsers that support it.
- Serve WOFF variant to the majority of browsers.
- Serve TTF variant to old Android (below 4.4) browsers.
- Serve EOT variant to old IE (below IE9) browsers.
If majority of your users use modern web browsers you can get away with specifying just two formats:
@font-face {
font-family: 'Roboto';
src: local('Roboto'), local('Roboto'),
url(fonts/Roboto.woff2) format('woff2');
}
Always check for the font locally
Some users might already have the fonts installed on their system, so why they should be forced to download the fonts again. You can prevent this by making sure you give precedence to local()
in the src
attribute of font face rule.
In the above sample note how the src: local
is used before the url
.
Optimnising the loading and rendering
By default the fonts are lazy loaded, which means the network request is delayed until the render tree is fully constructed, which in turn results in delayed text rendering.
There are three hooks you would need to implement a custom font loading strategy:
<link rel=preload>
- CSS
font-display
property - Font Loading API
Preload your web font
If there is a high chance that the font is used in the page, then you can load the font eagerly using the platform feature: <link rel=preload>
.
This will tell the browser that the font should be fetched early, but it won't specify how to use it. You will need to specify the appropriate CSS @font-face
:
<link
rel="preload"
href="/fonts/Roboto.woff2"
as="font"
/>
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
/* will be preloaded */
src: local('Awesome Font'),
url('/fonts/awesome-l.woff2') format('woff2'),
url('/fonts/awesome-l.woff') format('woff'),
url('/fonts/awesome-l.ttf') format('truetype'),
url('/fonts/awesome-l.eot') format('embedded-opentype');
unicode-range: U+000-5FF; /* Latin glyphs */
}
Warning: Not all browsers support this feature, but those who support it also support WOFF2, so you should use that format.
For more info on the other solutions refer to this great post.
Summary
In short these are the points you have to consider when optimising web fonts:
- Audit and monitor your font use.
- Subset your font resources.
- Deliver optimized font formats to each browser.
- Give precedence to
local()
in yoursrc
list. - Customize font loading and rendering using
<link rel="preload">
,font-display
, or the Font Loading API. - Specify revalidation and optimal caching policies
Hope this helps you move one more step in the right direction to improve your web application's performance.
Top comments (2)
The snippet at the end of the section Deliver optimised font formats specifies the local() directive twice...I was not sure if this was intentional or a typo. If it is intentional, could you please explain the reasoning for listing it twice? Thanks!
Once is enough 😉