I’m no performance expert but I use almost 200 KB of web fonts on this website, via Typekit, and I was wondering how it impacted performance.

After experimenting a bit with async loading, I noticed a peculiar problem on primed cache page loads (e.g. all page loads but the first one in any given session): in some browsers, especially in Safari, even when everything is cached there will be a very short Flash Of Unstyled Text (FOUT), for a fraction of a second.

fig 1. Micro-FOUT in Safari 7 on OS X

Granted, this is a problem that occurs with <script async …> and not while using Typekit’s official embed codes, so it’s a bit specific.

In this article I present the official options for using Typekit, my experiment with <script async …>, and how I battled the resulting Micro-FOUT by caching Typekit’s stylesheet URL for the session.

It’s a bit long-winded so you may want to skip to the parts that are of interest to you (if any).

  1. Typekit’s blocking loader
  2. Typekit’s script-injecting loader
  3. Trying to go async
  4. Caching Typekit’s stylesheet URL
  5. Final words

Typekit’s blocking loader

Typekit’s recommended loader for most users looks like this:

<!--
    “Copy and paste the embed code near
    the top of your site’s <head> tag.”
-->
<script src="//use.typekit.net/xxxxxx.js"></script>
<script>try{Typekit.load();}catch(e){}</script>

Typekit wants you to put these script elements before any other script and, more importantly, before any stylesheet. It’s also a blocking script, whose main function is checking for browser support and adding a <link rel="stylesheet" href="…"> element to the <head>, which loads a stylesheet with the @font-face rules (and base64 inlined font data) that match both the kit’s fonts and the browser’s preferred format.

In my case the Typekit JS is roughly 30 KB, and the CSS (with inlined font data) is 200 KB. Since we need to have the JS first, they’re loaded one after the other. A first page load looked like:

[== Typekit JS =>]
                   [==== Typekit CSS ====>]
                                            [== My CSS =>]

How much time loading Typekit’s files takes depends in good part on connectivity. In my tests over wifi it could take a full second, though I’ve seen articles that suggest it’s generally shorter than that… but also often longer on 3G networks.

On subsequent page loads in the same session, it’s still a blocking-script-that-inserts-a-link process, but since both the JS and the CSS are cached this process is near-instant (barring unusually long IO latency on the client).

I wanted to improve the first page load, though. I was especially concerned with blocked rendering on smartphones and tablets using 3G.

Typekit’s script-injecting loader

I could have used Typekit’s Advanced Embed code. It’s a short inline script that loads the main Typekit JS in a non-blocking way (aka asynchronously) by generating a <script> element. It also adds a wf-loading class to the root element while it loads so that you can adapt your CSS to mitigate the FOUT.

What’s that FOUT, you ask? The “Flash of unstyled text” (FOUT) is a web page rendering scenario in which some or all of the text in a website is rendered with CSS styles applied, but with a fallback font, until a web font is loaded and can be applied. This means that the text is painted once, then changes, either subtly or significantly. Many web authors consider this effect to be jarring and/or butt-ugly.

Personally, I don’t care much about the good old FOUT. I see We don’t want no stinking FOUT on our pages! as the new We want pixel-perfect! The FOUT is fine.

(Maybe it isn’t. But I’d love to see research data with actual users that indicates that the FOUT is problematic for users and results in users doubting or distrusting a brand, and/or lower transformation rates or whatever.)

Anyway. Recent browsers already mitigate the FOUT problem by hiding text-that-expects-a-web-font until the web font kicks in. Some browsers also use a timeout in case the web font doesn’t kick in, falling back to the next font in the stack (or default fonts). Firefox uses a 3000 ms timeout, and Chrome is landing a similar one.

Typekit’s Advanced embed code also tries to handle the FOUT, and offers more control to authors by providing CSS classes on the root element: wf-loading (initially), wf-active (if the Typekit fonts loaded alright and wf-inactive (if Typekit thinks web fonts are not supported, or after a timeout). Typekit’s doc suggests that authors could adapt font sizes to lessen the jarring effect of the FOUT, but I wouldn’t be surprised if most authors only used visibility:hidden on most text.

Is Typekit’s Advanced embed code useful? Some pros and cons:

  • Good: You can render the text as soon as possible, and use the Typekit CSS classes to soften the FOUT.

  • Good and bad: If you’re using them to hide the text until the fonts are ready, you’re just replicating browser behavior. But with wider browser support (since not all browsers have a timeout for unloaded fonts).

  • Bad: Script-injecting scripts is slow. This embed code uses a terse script to inject a <script> element, which means that the browser doesn’t know while parsing the HTML that there will be a JS file to fetch, so it can’t preemptively fetch it.

I wasn’t a big fan of that last point. Ilya Grigorik’s article “Script-injected ‘async scripts’ considered harmful” recommends using the async attribute rather than script-injecting <script> elements. I thought I’d give it a try.

Trying to go async

I didn’t care much about users seeing a FOUT on the first page load in a session, so I adapted Typekit’s blocking loader to use the async attribute:

<script async
        src="//use.typekit.net/xxxxxxxx.js"
        onload="Typekit.load()">
</script>

The goal here is to avoid blocking rendering on the first page load of the session. Since we’re not doing anything about the FOUT, we will have a FOUT then, which I don’t mind. But I don’t want a any FOUT on subsequent page loads if we can help it.

Why? Well I have this theory that users are more forgiving of (reasonably short) wait times and visual glitches on the first page load than they are when navigating between pages of a site. Because one action is perceived as loading a website and the other as loading a page (though this might not be true in all contexts). It’s a bit like how you might tolerate an app taking 2 seconds to load, but not tolerate if that app takes 2 whole seconds to switch between View A and View B. Different scenarios yield different user expectations.

(Once again, this is pure speculation. Existing or future Web usability research might disagree.)

My theory was that with the async script:

  1. On a first page load in a session, we would have a FOUT but at least the page would be rendered ASAP (possibly a win).
  2. On all the next page loads in the session (if any), Typekit’s main script and the relevant CSS with the font definitions and data would be fetched from the cache, so we would avoid any FOUT.

In practice, that second point didn’t work that well.

In my tests on OS X, it worked alright in Firefox (all the time) and in Chrome (most of the time), but not in Safari. Safari would often display the text with the fallback font, then a fraction of a second later (as short as 100 ms) with the web font. It’s like a FOUT happening almost instantly, with no time to see the fallback font but just enough time to see that the text is jumping around.

I find that while the regular FOUT is not so bad, a very short FOUT looks like a bad visual glitch. (Watch the video in figure 1 above if you haven’t seen it yet.)

What happens?

My guess is that even with the script and Typekit’s CSS cached, the page’s main styles seem to kick in just before Typekit’s CSS does. There’s a fraction of a second during which the browser thinks the first declared font in our font stack doesn’t exist.

How do we fix this?

We could go back to Typekit’s official loaders. We could also adapt their Advanced embed code to use <script async> and manage a wf-loading class.

Or we could cache Typekit’s CSS URL and thus avoid executing the Typekit script on every single page load.

Caching Typekit’s stylesheet URL

Typekit’s main script is roughly 30 KB and I don’t know everything it does. My assumption is that it checks for browser support, and whether the page’s domain is authorized, and generates a URL with an authorization token of some kind. For instance:

http://use.typekit.net/c/88e1f6/freight-sans-pro:n5,minion-pro:i4:i6:n4,source-code-pro:n4.TJD:N:2,XvF:N:2,XvL:N:2,XvJ:N:2,Y1M:N:2/d?3bb2a6e53c9684ffdc9a9afe195b2a6290e57de54ffd90397ef00df402a956cdd0eb2c07836803f6181a1e8df326e84d6c45bd52f48b03a33668db745de0a2d21a92ada4e2004a81ccf53844791c61a14ca0a2

However this URL and Typekit’s server-side authorization scheme works exactly, the URL generated by Typekit’s script running on a user’s browser at the start of a session will map to a stylesheet with all the relevant @font-face rules with base64-encoded font data (unless that browser is not supported).

The browser will then cache that CSS, for that URL. Of course we know that the browser cache can be brittle and short-lived, but the assumption here is that it will work in most cases for all page views but the first one in a multi-page browsing scenario. That’s the scenario we’re trying to optimize for (after having optimized — though that’s debatable — the first-load scenario using standard async).

So what we want to do here is:

  1. On first page load, load Typekit’s script with <script async …>.
  2. Once Typekit’s stylesheet is loaded, we get the URL and cache it.
  3. On secondary page loads in the same session, we don’t load Typekit’s script, and instead generate a <link rel=stylesheet …> with our cached URL. Browsers will then use the CSS they cached in most cases.

What could go wrong?

We fail at caching the stylesheet’s URL? Well, we end up with a <script async …>, which works perfectly fine in most browsers. We just have to live with occasional micro-FOUT.

We cached the URL but the browser doesn’t have the CSS in its cache for some reason? It will then request the CSS from Typekit, and we still save some time not requesting and/or executing Typekit’s JS.

We cached the URL, the browser still requests it, and Typekit returns a 403? It’s not very likely but not good. So we have to take care of this scenario and fall back to… script-injecting. That means 3 requests (wrong CSS, Typekit’s script, then the good CSS) instead of two, which is not good, but I expect this situation to happen quite rarely (if at all).

SessionStorage implementation

I first wrote a script that caches the stylesheet’s URL in sessionStorage. It doesn’t use localStorage because keeping the URL for too long might result in more trouble than it’s worth (CSS not in cache, Typekit server returning a 403 because the token expired or something, browser asking for an older version of your webfonts kit). SessionStorage is a good fit for what we want to do: optimize secondary page loads (if any).

Here is the full script: loader-sessionstorage.js.

It’s a 0.8 KB loader script (without minification or compression) that should be inlined in the <head>, possibly before your main CSS (but I haven’t tested that much). Here’s a summary of the code:

function makeScript () {
  // Make a <script> element loading Typekit’s main script
  // We use Typekit’s scripted events to know when the
  // stylesheet is loaded and then call cacheUrl()
}

function makeLink () {
  // Make a <link rel=stylesheet> with the URL from
  // sessionStorage. Add an event listener: if it fails
  // to load, call makeScript()
}

function cacheUrl () {
  // Get the URL of the inserted <link>
  // and put it in sessionStorage
}

if (/* we don’t have the URL in sessionStorage */) {
  // First page load scenario
  // We script-inject Typekit’s main script, which
  // will inject the relevant stylesheet on its own,
  // and then we cache the URL in sessionStorage.
  makeScript()
} else {
  // Secondary page load scenario, we just generate
  // the <link> element (but listen on errors).
  makeLink()
}

In my tests this seemed to work well, but it was not perfect in Safari, which still had the micro-FOUT once in a while (though not on all secundary page loads, so that’s better).

One problem with this approach is that the <link> element doesn’t exist in the HTML, so the browser cannot request it or — more probably in our scheme — know to apply the stylesheet that it cached earlier. That’s probably the reason we still have a micro-FOUT on some page loads.

Using server-side sessions

Since my site runs on PHP, I opted for a PHP session. It adds a cookie (with lives until the browser session’s end), but with PHP (or any other server-side scripting language) we can serve the relevant HTML in both scenarios.

When we don’t have a cached URL (probably the first page load in this session):

<script async
        src="//use.typekit.net/xxxxxxxx.js"
        onload="Typekit.load()">
</script>

And when we do have a cached URL:

<link rel="stylesheet" href="http://use.typekit.net/…">

This is the crux of our implementation, but we still need a little bit more client-side magic for two things:

  1. On a “first” page load: caching the URL.
  2. On a “secondary” page load: recover from a CSS loading error.

Here’s the code I used for that, on the “first” page load:

<script>
function _tk_cacheUrl() {
  var selector = 'link[href*="use.typekit.net"]:not([onerror])'
  var http = new XMLHttpRequest()
  var data = 'url=' + document.querySelector(selector).href
  http.open('POST', '/assets/utils/cache-typekit-url', true)
  http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
  http.setRequestHeader('Content-length', data.length);
  http.setRequestHeader('Connection', 'close');
  http.send(data)
}
</script>
<script async
        src="//use.typekit.net/xxxxxxxx.js"
        onload="Typekit.load({active: _tk_cacheUrl})">
</script>

And on secondary page loads:

<script>
function _tk_makeScript() {
  var s = document.createElement('script')
  s.src = '//use.typekit.net/xxxxxxxx.js';
  s.onload = function(){ Typekit.load({active: _tk_cacheUrl}) }
  document.querySelector('head').appendChild(s)
}
function _tk_cacheUrl() {
  var selector = 'link[href*="use.typekit.net"]:not([onerror])'
  var http = new XMLHttpRequest()
  var data = 'url=' + document.querySelector(selector).href
  http.open('POST', '/url/to/session-caching-script/', true)
  http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
  http.setRequestHeader('Content-length', data.length);
  http.setRequestHeader('Connection', 'close');
  http.send(data)
}
</script>
<link rel="stylesheet"
      onerror="_tk_makeScript()"
      href="http://use.typekit.net/…">

The _tk_cacheUrl() function sends a POST request to a local server-side script that will receive the URL from Typekit’s injected <link> element. I’m not providing my PHP script because my PHP is probably worse than my JavaScript. What’s more I don’t want this article to be a copy-paste solution — It’s an experiment! It may break your site!

The _tk_makeScript() function is similar to Typekit’s Advanced embed code, except without all the parts about managing classes on the root element, and we’re using Typekit’s JS font events to trigger our caching function.

Note that in that second scenario (when we have a cached URL in the session data), we could do away with the _tk_cacheUrl() function, but we’re still including it so that if something goes wrong and we fall back on injecting Typekit’s script, we can still cache the (probably new) stylesheet URL then.

Final words

Look out for…

  • This is highly experimental. It may break webfonts on your site.
  • It may yield worse performance than Typekit’s recommended loaders.
  • It has a FOUT for the first page load in the session. That’s on purpose.
  • It doesn’t use Typekit’s status classes (wf-loading etc.).

A more complete solution?

If you wanted to develop a more complete solution using server-side caching of the URL, you could port Typekit’s Advanced embed code to <script async …>, while retaining its other features (classes on the root element, timeout). Our two scenarios would look like:

<script>
void function _tk_events() {
  // Add class(es) to to root element, timeout, etc.
}()
function _tk_makeScript() { … }
function _tk_cacheUrl() { … }
</script>

<!-- No URL in session: -->
<script async
        src="//use.typekit.net/xxxxxxxx.js"
        onload="Typekit.load({active: _tk_cacheUrl})">
</script>

<!-- OR if we have the URL in sesion: -->
<link rel="stylesheet"
      onerror="_tk_makeScript()"
      href="http://use.typekit.net/…">

I’m not using Typekit’s classes at all, so I haven’t tried to port the full feature set of Typekit’s Advanced embed code, but from what I’ve seen it wouldn’t be hard to do.