Minimising font downloads

Optimising fonts is pretty difficult for larger sites. There's an easy solution, although only some browsers support it.

Translations

Fonts can be big

Really big. They can be anywhere from 70k to many megabytes (compressed of course, because why wouldn't you?). You want bold? Well, you just doubled the size. Italic? Tripled. Bold-italic? Quadrupled. This is a huge deal if those fonts are blocking the rendering of primary content, which they often are.

Sub-setting the font helps a lot by getting rid of all the characters that aren't needed. That usually brings the font under 50k.

But what characters do you keep? Pretty tough to answer if your site has a lot of content, or if users can create content. If characters outside the subset are used, they'll fallback and look pretty ugly.

Cohérent
Note how the 'é' is a different typeface to the 'e'

You either live with the occasional ugliness, or be conservative with your sub-setting which lands you back with a heavy font download.

But there's a better solution!

Just let the browser deal with it

Take a rough guess at which chars most pages will use, create a subset, but create another resource containing additional characters:

/* Small subset, normal weight */
@font-face {
  font-family: whatever;
  src: url('reg-subset.woff') format('woff');
  unicode-range: U+0-A0;
  font-weight: normal;
}
/* Large subset, normal weight */
@font-face {
  font-family: whatever;
  src: url('reg-extended.woff') format('woff');
  unicode-range: U+A0-FFFF;
  font-weight: normal;
}
/* Small subset, bold weight */
@font-face {
  font-family: whatever;
  src: url('bold-subset.woff') format('woff');
  unicode-range: U+0-A0;
  font-weight: bold;
}
/* Large subset, bold weight */
@font-face {
  font-family: whatever;
  src: url('bold-extended.woff') format('woff');
  unicode-range: U+A0-FFFF;
  font-weight: bold;
}
/* And use the font: */
p {
  font-family: whatever, serif;
}

The above contains multiple definitions for the same font-family name but with different font-weight & unicode-range combinations. The unicode range U+0-A0 covers basic letters numbers and punctuation. This tells the browser that a bold 'e' glyph can be found at bold-subset.woff, whereas a normal weight 'é' can be found at reg-extended.woff

A browser can be smart about this and fetch the minimum required to render the page. If the page uses characters outside of that range, the extra font is downloaded in parallel.

You should still provide a suitable fallback in case the font doesn't download in time or the browser doesn't understand the format.

Demo

Take a look.

Optimally, the browser should only download the normal sub-setted resource. If you edit the text to include, say 'ö', the browser should fetch an additional font.

Browser support

Ok, here's the bad news:

  • Safari: Downloads all the fonts
  • Internet Explorer: Also downloads all the fonts
  • Firefox: Only downloads one font, but the wrong font, leaving rendering broken
  • Chrome: Does the right thing, only downloads the normal subsetted font
  • Opera: Same as Chrome

This is pretty bad new for IE and Safari, they end up downloading 300k rather than 30k in this example. It gets much worse if you add in italic & bold-italic, as IE & Safari will download those too, even if they're not used.

What's going on with Firefox?

Incorrect Firefox rendering

Firefox does the right thing with font-weight, it only downloads what the page uses, unfortunately it ignores unicode-range. The "reg-extended" font declaration overwrites the "reg-subset" one, but only because of the source order. "reg-extended" is used to render the page, but it doesn't contain all the characters, so fallbacks are used. Of course, "sans-serif" would have been a better fallback, but I wanted to highlight the problem.

But if it's only using the extended font, how are most of the characters are rendering correctly? You wouldn't expect 'o' to be part of the extended set, but it is. In order to be efficient within the font, ö is a combination of the o and ¨ glyphs. Although we didn't want to keep 'o' in the extended font it's retained because other glyphs depend on it.

Einstein's theory of glyph reuse

No glyphs reuse 'F' or 'b', so they're not in the extended font.

Firefox isn't paying attention to unicode-range at all. As well as providing a hint for downloads, unicode-range dictates which characters the browser should use from the font, even if it contains others.

You can work around the Firefox issue by including all glyphs in the final fonts listed for your subsets ("reg-extended" and "bold-extended" in this case), or separate your subsets into different font families:

/* Small subset, normal weight */
@font-face {
  font-family: whatever;
  src: url('reg-subset.woff') format('woff');
  unicode-range: U+0-A0;
  font-weight: normal;
}
/* Large subset, normal weight */
@font-face {
  font-family: whatever-extended;
  src: url('reg-extended.woff') format('woff');
  unicode-range: U+A0-FFFF;
  font-weight: normal;
}
/* And use the font: */
p {
  font-family: whatever, whatever-extended, serif;
}

Here's a demo of that. Firefox downloads both fonts regardless of whether it needs them, but at least the rendering is correct.

Push for this to be fixed!

When it works, this is a great feature, especially for sites that handle a variety of locales, sites that allow users can submit their own content, or even just for downloading that fancy-ampersand font only when it's needed. If it's useful to you, tell browser vendors (including your use-case):

In the meantime

If you're already serving a large font, consider splitting it into multiple files with different unicode-ranges. Give each of them a different font-family name to work around Firefox issues.

IE, Firefox & Safari will download more than they need, but only the equivalent of the one large font they had before. Chrome and Opera users will get a faster experience, and hopefully this enhancement will land in other browsers.

Further reading

  • WOFF2 - supported by Chrome & Opera, reduces font resources by a further ~20%
comments powered by Disqus