Basic Optimizations - Web Fonts Performance - Responsive Web Design, Part 2 (2015)

Responsive Web Design, Part 2 (2015)

Web Fonts Performance

Basic Optimizations

Before moving on to advanced font loading methods, you should first check if you have the basics right. Often, the most performance can be gained by doing some simple optimizations, such as caching, compression and subsetting. Another simple, and often overlooked, optimization to consider is to reduce the number of fonts you’re attempting to load. For the best performance, you should aim to load a maximum of three or four variations. Carefully evaluate your content and design before attempting to load more than that.

FALLBACK FONTS

One of the best ways to give the appearance of improved performance is to define proper fallback fonts for your web fonts. You should aim to find a font with a similar x-height (the height of the lowercase characters) and em-width (the width of characters) so as to reduce the visually jarring reflow that happens when a fallback font is replaced by a web font.

Nathan Ford wrote an excellent article on how to construct a good font stack14. He recommends giving font stacks for body text and headlines a different structure. For example, a font stack for body text consists of four types of font families.

font-family: Ideal, Fit, Common, Generic;

The “Ideal” font is the one you wish to use while all the others are fallback fonts. “Fit” is something that is close in both style and metrics to your “Ideal” font, and available on a significant portion of your visitors’ devices and operating systems. “Common” is a font family that is common on most platforms and still (roughly) matches your design. Generic is one of the generic font family names: serif, sans-serif, monospace, etc. Font stacks for headlines are similar but differ in the qualities that should be looked at when selecting a headline font (for example, in headlines, style should be favored over metrics).

Selecting fallback fonts is no easy task. Blindly copying and pasting “known” font stacks is never a good idea. Each platform and device has a different set of locally installed fonts. Some fonts are included with the operating system while others are installed by applications that every user may not have. Two good tools for finding out which font families are available on a given platform are fontfamily.io15 by Zach Leatherman and Tinytype16 by Jordan Moore.

Combining these tools with the usage statistics of your website will let you simplify your fallback stack by using the fallback fonts that most of your visitors will have. This simplifies testing your font stack. Always test your site with web fonts disabled so you can test the fallback behavior and user experience (like any other progressive enhancement). Go through each fallback font in your stack and test them on the operating systems and devices that your target audience uses.

Another important issue to keep in mind is the difference in variations (weight, style, etc.) between locally installed fonts and web fonts. For instance, consider what would happen if you load a web font with a thin weight, but your fallback font only supports regular and bold weights. If your web fonts fail to load, you might be in for quite a design surprise.

Theoretically, the best way to avoid the visual discontinuity between web fonts and fallback fonts is to use metric-compatible fonts. Metric-compatible fonts are fonts designed to have identical metrics to another font so they can be used as a substitute without affecting page layout. Each character in a metric-compatible font has the same width and height as the same character in another font (the font whose metrics it is compatible with). Several such fonts have been designed. Perhaps the most famous example is Arial, which is metric-compatible with Helvetica. If your web font is Helvetica, an excellent fallback font (from the point of view of minimizing metrical changes) would be Arial. However, in practice, metric-compatible fonts are of limited use. There simply aren’t enough of them available as either locally installed fonts or as web fonts to serve as good fallback fonts.

CACHING

The simplest way to improve caching is to set HTTP response headers with a long cache expiration time. This works really well until you need to update the resource (cache invalidation). A common way to solve this is to give each resource a unique filename based on a hash of its content. Each time the contents of the files change, the hash and thus the filename will also change. Once the HTML or CSS file that references the resource falls out of cache (which should have a shorter cache expiration time), the latest resource versions will be downloaded. This lets you set long expiration times on resources and have the ability to update them. To learn more about this approach to caching read the Google Web Fundamentals caching guide17.

This approach also works really well for fonts, because they change infrequently. Setting an HTTP Cache-Control response header with a long max-age value for both the CSS and font files is an easy way to significantly improve the performance of your site. It is a good idea to cache both the CSS and font files for at least one week by setting the Cache-Control header with a max-age value of 604800 (60 seconds × 60 minutes × 24 hours × 7 days).

Cache-Control: public, max-age=604800

A useful HTTP cache control extension is stale-while-revalidate18. If this property is set on a resource, browsers will use the cached version of a resource even though it has already expired (and should technically be retrieved from the server). They will then asynchronously make a request to the server to retrieve the latest version of the resource, and update the cache, so the latest version of the resource is used on the next request. Like the max-age property, stale-while-revalidate takes a number of seconds that indicates the amount of time the response can still be used even though it has expired.

If you cache your resources for one week, a good stale-while-revalidate value would be two weeks or 1209600 seconds (60 seconds × 60 minutes × 24 hours × 14 days). This will keep your resources reasonably up to date and the longer grace period will make sure returning visitors do not block on downloading content they already have in cache.

Cache-Control: public, stale-while-revalidate=1209600, max-age=604800

This property can be used for all HTTP resources, but it is especially useful for font files, which — even though they might be stale — can still be used to render content, and thus not block your page. At the time of writing only beta versions of Chrome and Opera support this property. Older browsers will ignore the property, so it is safe to set the property on your resources.

COMPRESSION

There are two ways to compress fonts: compressing the data inside font files, and compressing fonts when they are sent from the web server to browsers. Most web servers can compress assets using gzip, and all modern browsers will decompress them transparently. You should turn on this feature for font files as well, but it isn’t necessary to do so for all font formats.

The WOFF2 and WOFF formats are already compressed, so they don’t gain anything by having the web server compress them again. The OpenType and TrueType formats do not have compression built in, so they should always be compressed by your web server. Even though the EOT format supports compression, many tools do not generate compressed EOT files, so they should be compressed by your web server.

You can gain even better compression rates by compressing your font files (and other assets) using Google’s Zopfli compression algorithm19. Zopfli generates gzip-compatible output, so it can be used where gzip is supported. In fact, Zopfli can also be used as an algorithm for compressing the font data inside WOFF files using my own modification of Jonathan Kew’s sfnt2woff20, called sfnt2woff-zopfli.

Using sfnt2woff-zopfli to compress your WOFF files will result in an average of two to six percent additional savings compared to the standard compression used by WOFF.

INLINING

A common trick for reducing load times is to inline fonts in a separate CSS file by using base64-encoded data URIs. This avoids the additional overhead of an extra HTTP request for each font variation. While this might seem like an attractive option, there are many reasons why inlining fonts is not a good idea. Guy Podjarny’s Performance Calendar article, “Why Inlining Everything Is NOT The Answer21”, is a great resource on the disadvantages of inlining resources. The most obvious reason not to inline is that it limits you to a single font format. Inlining multiple formats in the same CSS file is a bad idea because browsers will download the entire file whether or not they support the formats. You could work around this problem by using a different style sheet for each font format and switching between them based on the user agent string, but this is error-prone (user agent strings often lie). Inlining also doesn’t work for formats that cannot be inlined; for example, Internet Explorer does not support inlining EOT files as data URIs.

Inlining also runs counter to browsers’ ability to download multiple assets in parallel. It is more efficient to download multiple font files in parallel than as part of one big file. The base64 encoding also adds a 20–30% overhead to your font’s file size, which further reduces the benefits of inlining (though this can be negated by applying gzip or Zopfli compression to your CSS files as described in the previous section). Last, but not least, check your font license before deciding to inline fonts: inlining is often explicitly prohibited.

As a general rule, you should not inline fonts in CSS files unless you have a very specific use case, such as supporting only a single web font format or browser, storing fonts in localStorage, or low-bandwidth and high-latency connections.

SUBSETTING

Most fonts support glyphs (characters) for several languages and scripts. The more glyphs included in a font, the larger its file size will be. Unlike locally installed fonts, web fonts need to be downloaded over (possibly) unreliable or slow network connections each time they are used. We have an interest in keeping the file size as small as possible, so the next optimization after selecting font formats is to subset the font, by removing characters and features you don’t need.

For example, if your site is written exclusively in English you don’t need the glyphs for other languages and scripts in your web font. Removing them will reduce the file size of your web font significantly.

A good tool for creating static subsets is FontSquirrel’s Web Font Generator22. Its expert mode lets you define any custom subset, and it will automatically generate the correct subsets and font formats for you. Be careful when subsetting a font: names and places often contain characters you normally wouldn’t use in English. Another thing to be mindful of is the license agreement that comes with the font. It often does not permit custom subsets. Contact the font foundry if your license doesn’t permit subsetting or if you have any questions about subsets — they’re often happy to help.

Subsetting works well for languages and scripts that have clearly defined boundaries. Nevertheless, subsets are not the answer to all large font files because browsers treat subsets as two separate fonts. This means that OpenType layout features (such as ligatures and kerning) that span multiple glyphs stop working across subsets. For example, scripts like Arabic are difficult to subset because they contain a large number of characters that can be used in almost any order, and often include (required) ligatures and kerning information for many character combinations. Creating subsets for these scripts at the wrong point could result in failure to display a required ligature or incorrect kerning because the characters are in different subsets. Be careful when you subset your fonts. It is usually better to first load a minimal subset followed by a larger superset, than two or more complementary subsets.

You can help browsers choose which subset to download based on your page content by using the unicode-range property in your @font-face rules. This property tells browsers which characters are included in the font, so they can only download the fonts when they are needed (because the characters are used on the page). For example, if most of your content is written in English and a couple of your pages contain Cyrillic you can create two subsets.

@font-face {

font-family: My Font;

src: url(myfont-english.woff);

unicode-range: U+20-7E;

}

@font-face {

font-family: My Font;

src: url(myfont-cyrillic.woff);

unicode-range: U+0400–U+04FF;

}

The browser will look at which characters are used on the page, and if a match is found within one of the @font-face rule’s Unicode ranges, the font is downloaded. If a page only used characters within the Unicode range for the English subset, it will only download the English font file and not the font containing all Cyrillic characters. Likewise, if a page only contains Cyrillic, the English subset will not be downloaded. Both subsets will be downloaded if the content is mixed English and Cyrillic.

IE8

IE9

IE10

IE11

Chrome

No

Partial

Partial

Partial

Yes

Firefox

Safari

Safari (iOS)

Opera

Android WebKit

No

Partial

Partial

Yes

Partial

Browser support for the unicode-range property. Full support indicates browsers use Unicode ranges to selectively download and display fonts. Partial support indicates browsers that only display the characters listed in the range, but do not use the information to optimize font downloads.

Unfortunately, this useful property is only supported in a handful of browsers. While most WebKit-based browsers support the syntax, only Chrome and Opera download fonts selectively based on the unicode-range property. At the time of writing, Firefox and Internet Explorer do not support the unicode-range property (though Firefox has an implementation in progress and the Internet Explorer team has shown signs of interest as well). The fallback behavior when this property is not supported is not ideal, but workable: browsers will download all font files that match the font family required to render the content (even though some of them may not be used).

If the content on your site is mostly or entirely static, it is possible to create a perfect subset for your content. This requires finding all characters in your content and using them to create a subset. This works great for sites that are rarely (if ever) updated, but is problematic for dynamic sites because a new subset needs to be created each time the content introduces characters that are not in the existing subset. You’ll need to find a trade-off between character support, file size and cacheability.

Besides static subsetting, there are two additional subsetting approaches: dynamic subsetting and dynamic augmentation. Both work by examining the content on a page and creating the perfect subset for it. The difference between these two methods lies in how they handle updates to existing subsets.

The idea behind dynamic subsetting is to create new subsets for any content not covered by existing subsets. For instance, let’s say you run a blog network. There is no way to anticipate what kind of web font character set support you need — people might write and publish content in all sorts of languages. Using dynamic subsetting you can load subsets based on the content of a page instead of deciding on the subset beforehand (i.e. statically). Subsets can also be created for content that is dynamically inserted into the page (a news widget, etc.). This has been described in great detail by Nathan Ford in his article, “Adventures in Dynamic Subsetting23.”

Dynamic augmentation is very similar to dynamic subsetting. It also dynamically creates subsets based on the content, but instead of creating new subsets, it will update the existing subset so that it covers the characters required by the new content. Dynamic augmentation does this by adding the new characters to the existing subset (thus creating a new subset). So instead of having multiple subsets on a page as with dynamic subsetting, dynamic augmentation will always only have a single subset that is updated as required.

While the end result for both methods is the same (one or more subsets, that cover all the content on a page) there is an important distinction. Recall that browsers treat subsets as separate font files, and that OpenType features will not work across multiple subsets. While this is a minor inconvenience for Latin-based languages, it is a major problem for complex scripts (such as Arabic) where OpenType features are required to display text properly. This is where dynamic augmentation shines. Instead of creating complementary subsets, it will update an existing subset with new characters and OpenType features. The result is that OpenType features work correctly because the subset is a single font file.

Dynamic augmentation is also a necessity for scripts that are too large to be served as a single subset. Typically, you should try to keep your individual font files below 100KB — but the average Asian font is several megabytes. Dynamic augmentation would, for example, let you load the characters for your primary content in a single and small request so it can be displayed quickly. You can then asynchronously update the subset to include character support for all your content. Because these fonts are dynamically constructed client-side using JavaScript, it is difficult to cache them. This is where technologies like Service Workers24 could be helpful, enabling a background process to explicitly cache a dynamically augmented font.

Google has released the source code for their font utility sfntly25 which can be used as a base for dynamic subsetting. There are currently no open source subsetting engines that can perform dynamic augmentation. This means that — for now — you’ll need to rely on font service providers to do this for you. Currently only Google Fonts, Typekit, and Fonts.com offer dynamic subsetting.