zachleat’s Ugly Mug

Zach Leatherman

Web Font Anti-pattern: Data URIs

11 March 2016 Read in about 7 minutes #20 most popular

After I posted my Critical Web Fonts article on Twitter, I had an interesting conversation with a developer named Wim Leers about embedding Web Fonts as Data URIs.

His suggestion was to embed the font directly in a style block on the server rendered markup, something like:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<style>
	@font-face {
		font-family: Open Sans;
		src: url("data:application/x-font-woff;charset=utf-8;base64,...") format("woff");
		font-weight: 400;
		font-style: normal;
	}
	</style>
</head></html>

This approach should not to be confused with the asynchronous loadCSS Data URI approach documented (but deprecated) on the Filament Group blog.

I’ve seen a similar variant of this Data URI approach used by Alibaba.com, although their approach used an external stylesheet rather than an inline style element. I talked a little bit about it at Velocity last year.

I consider this approach to be an anti-pattern for normal font loading scenarios for a few reasons:

  1. It puts a large Data URI in the critical path. Remember that CSS blocks rendering. The goal here is to avoid a Flash of Invisible Text (FOIT) and minimize our Flash of Unstyled Text (FOUT). It obviously isn’t a good tradeoff to delay the entire page render to avoid FOIT and FOUT. Since 42% of web sites load more than 40KB of web font page weight, many sites would need to put 40KB of Data URIs in their critical path, far exceeding the recommended 14KB window for critical content.
  2. The font format you embed is probably not optimal. If you embed a Data URI, you’ll probably embed the WOFF format to give you ubiquity (better browser support) even though the WOFF2 format usually has about a 30% smaller footprint. Embedding a single format removes the benefit of automatic format selection that a typical comma separated src attribute provides. You aren’t required to list only one src here, but for example let’s say you embed a WOFF2 format Data URI and list the WOFF format as an alternate external url in the src attribute. There are still quite a few modern browsers that don’t support WOFF2 and they would load that big Data URI and still have to resort to using a fallback format URL. (See Appendix 1, Data URI and Fallback src below.)
  3. Ability to cache fonts suffers. This approach worsens with repeat views because the Data URI is tightly coupled to the markup and will not be cached (unless the user visits the same destination twice).
  4. The other drawback Bram Stein mentions in his latest presentation (and has a great waterfall showing it, too): if you have multiple web fonts, making them all Data URIs forces them to be loaded sequentially (bad) instead of in parallel (good).

Update: Wim Leers has since informed me that the approach he was proposing was not inlined critical CSS, but rather a blocking CSS stylesheet, a la the approach used by Alibaba and Medium. The above drawbacks still stand, save for #3. What’s more, this approach probably exacerbates drawback #1, given the performance gains we already know exist when using Critical CSS.

For those reasons, this method is considered to be an anti-pattern and should not be utilized on a production site. It may seem superficially beneficial, but it’s actually bad for performance.

But just for the sake of argument, let’s put it into action and see how it affects the fonts on my web site:

(Times generated using Chrome Canary’s Developer Tools Network Throttling in Regular 3G mode)

Default Font LoadingRoman Data URIand Italic Data URIand Bold Data URIand Bold Italic Data URI
Initial Render573ms
 
54KB HTML
953ms
(+66%)
95.7KB HTML
1.27s
(+121%)
133KB HTML
1.94s
(+238%)
175KB HTML
2.30s
(+301%)
212KB HTML
Roman Loaded2.12s1.01s
(-52%)
1.53s
(-28%)
2.03s
(-4%)
2.38s
(+12%)
Italic Loaded2.12s2.05s
(-3%)
1.53s
(-28%)
2.03s
(-4%)
2.38s
(+12%)
Bold Loaded2.20s2.11s
(-4%)
2.16s
(-2%)
2.03s
(-8%)
2.38s
(+8%)
Bold Italic LoadedN/AN/AN/AN/A2.38s

Interestingly enough, if you only inline the Roman version and you’re willing to sacrifice almost 400ms of extra time for initial render (wow, that is a big sacrifice), you can cut a whole second off the Roman web font rendering time. Initial render suffers even worse with a second inlined font, and if you inline more than two fonts (ignoring web font SPOF concerns) performance-wise you’re better off doing nothing (compare the 1st and 4th columns). Also note that I didn’t test repeat views in the above table because the data was conclusively negative without it.

But wait…

The discussion started with embedding the entire web font, but what if we apply this idea to the Critical FOFT approach? What if we only inline the critical subset font? My hunch is that the subset 11KB WOFF font file (much smaller than the 40KB average) is probably still too large to put into the critical path, but let’s test it out.

(Times generated using Chrome Canary’s Developer Tools Network Throttling in Regular 3G mode)

Update March 17, 2016: Per a discussion with @pixelambacht, we were able to optimize the WOFF font file to only 5KB! I’ve updated the results below. This shaved about 5-10 more milliseconds off in the tests that I ran, so depending on your design preferences you should make your own choices about whether those optimizations are worth it.

Empty Cache Visit
Critical FOFTCritical Roman Data URI
Initial Render570ms
 
58.2KB HTML
580ms
(+1.7%)
65.4KB HTML
Stage 1 Render
(Critical Roman)
967ms580ms
(-40%)
Stage 2 Render
(Roman, Italic, Bold, Bold Italic)
2.70s2.42s
(-10%)

Wow! No visible FOUT! This is a huge deal. The critical web font is available on first render with an empty cache on 3G. This is great! The only problem here, of course, is that for repeat views the Data URI is still inlined on the page. Let’s test that out:

Repeat Views
Critical FOFTCritical Roman Data URI
Initial Render309ms293ms
(-5.1%)
Critical Roman Loaded479ms435ms
(-12%)
Roman Loaded479ms435ms
(-9%)
Italic Loaded479ms435ms
(-9%)
Bold Loaded479ms435ms
(-9%)
Bold Italic Loaded479ms435ms
(-9%)

Times look marginally better here too. Huh. I think I’m gonna roll with this approach on my website and see how it plays out live. Thanks for the discussion Wim! I guess I learned here that just because something is an anti-pattern doesn’t mean you should throw the baby out with the bath water. You might get some benefit from using a piece of the approach. Data URI Critical FOFT!

Appendix 1, Data URI and Fallback src

@font-face {
	/* In many browsers it loads the giant Data URI but isn’t able to use it */
	src: url("data:application/font-woff2;charset=utf-8;base64,...") format("woff2"), url( /path/to/webfont.woff ) format( "woff" );
}

Retweet to share this post