For all of the beauty that webfonts have brought to the internet, they have also brought some performance challenges. I recently found some fascinating characteristics of Typography.com’s webfont offerings. Typography.com offers fonts from Hoefler & Co., which are highly acclaimed for being of a particularly high quality. Previously unavailable on the web, Typography.com made Hoefler & Co’s fonts available in the middle of 2013.
Loading webfonts is a very interesting enterprise in 2014. It seems that everyone loads fonts a little differently. Google Fonts uses CSS to dynamically load different external fonts for different browsers. Typekit bootstraps its fonts with minimal JS that loads in CSS with different data URI encoded fonts depending on the browser. Both of these webfont hosts also offer loading fonts with a JS API that offers additional levels of control. Site owners can also load fonts from their own sites in various manners. Finally, a Font Load API is slated for release in Chrome later this year, which gives even more options for loading fonts.
While preparing an example for an upcoming web performance talk, I was analyzing Post Status using WebPageTest.org (WPT). As a WordPress developer heavily involved in the community, I am a frequent visitor to this site. In my many visits, I noticed that there was this slight delay in delivering the page. There’s almost a little “stutter” of sorts prior to the page rendering. There’s just a brief flash of white that is barely noticeable, but it is something that I would see from time to time. I set out to see if I could figure out what exactly was going on.
After running the site through WPT, I reviewed the median run after 9 runs through Chrome. What I noticed right away was that the start render time was 2.993s with a visually complete time of 3.100s. This is not good and seems to explain my experience of the “stutter”. With a time to first byte of 527ms, it suggests that the WordPress application is loading the site quickly enough and cannot explain the slowness. The HTML is delivered and it takes ~2.5s until the first pixels are painted on the screen. This is slow. Additionally, it’s fairly typical to see the first paint occur and more time to pass before it is visually complete. In this case, there was only 107ms between first paint and visually complete. This pattern suggests that something was blocking the browser from rendering.
Let’s back up and talk about how a browser paints pixels to the screen. When a browser requests a page, it downloads HTML from a server and begins parsing it. It does does two really important things during this process: 1) parses the HTML into the DOM, and 2) onstructs the CSSOM from the CSS. As soon as these processes are complete, the browser has a full render tree and can start a layout event (position elements in the screen) and paint event (apply non-layout styling to the elements). The faster that the DOM and CSSOM can be constructed, the faster the initial painting of screen occurs. JS plays a major role in this process. When JS is linked in the document or executed inline, browser rendering is halted. Additionally, JS is not executed until the CSSOM can be constructed. As such, JS execution early in a page load has a significant impact on start render time. Ultimately, this complex dependency tree resolution has led to the recommendation that CSS should be loaded in the head and JS in the footer. It is imperative that for fast loading pages that CSS gets to the browser as soon as possible and is not blocked by JS.
Turning back to Post Status’ resource waterfall, I tend to like to explore what was happening around the time of first render to understand more about what may have caused a delay in the render. By working back from this point, I can usually determine what the necessary elements were to produce an initial render. The thing that initially caught my eye was item 27 due to the long horizontal bar that is created. This is a CSS file served from the Post Status server that includes data URI encoded fonts. In total, it’s taking 1797ms to load. That certainly could be faster, but that isn’t the main issue here. Looking up a bit further in the waterfall, I noticed that there was a second CSS file related to fonts (line 12) that was served from cloud.typography.com (note that you will get a 403 error with this URL as it needs to accessed from the whitelisted site). What happens in this case is that the CSS file from Typography.com is accessed and causes a 302 redirect to the font file on the site’s server. This is a fascinating approach to maintain the actual font files on the local server while maintaining the logic for determining which fonts to server for which browser, as well as the authentication of the site, on Typography.com’s servers. Typekit handles this authentication and browser compatibility via JS, while Google Fonts handles the browser compatibility via a CSS request.
Now, why in the world does this cause the start render delay? One would think that Typography.com is on to something by queueing up the fonts with CSS files and not involving JS. This follows the best practice of loading CSS in the head while avoiding render blocking JS; however, in this case the strategy backfires severely. Because they have data URI encoded the fonts into the CSS, this significantly delays the time it takes to download the CSS file and thus construct the CSSOM. With the CSSOM construction being delayed by the ~1.7s it takes to download the CSS fonts, start render is stalled. It’s important to note that this strategy guarantees that the fonts must be downloaded in order for the page to start rendering. In contrast, Google links to external font files in its CSS. The CSSOM can be fully constructed by the time that the CSS file is processed, which means that the page can start rendering. The fonts would be queued up at the time that the first paint is occurring. Yes, this means that the start render would occur without the font initially being displayed; however, there is a psychological advantage to getting the page painted more quickly. Users will perceive that something is happening and thus think that the page is loading more quickly. In Typography.com’s case, the render is delayed because the CSS containing the full font files must be resolved first.
There are a few more problems with Typography.com’s approach. The CSS file that is loaded from Typography.com is going to be the only file that is loaded from this URL. This triggers a DNS lookup, new TCP connection, and file download, all of which will always be nothing more than a 302 redirect. In my sample, this process takes 107ms. Downloading these fonts will always incur that 107ms penalty for nothing more than a redirect. I am guessing the CSS file is generated dynamically too so one lucky visitor for each browser will likely incur even worse load times (Unfortunately, in the case of Post Status, this redirects to the non-www version of the domain, which leads to more DNS lookups and TCP connections. This is a simple misconfiguration that could be easily fixed). What’s worse is that file is not browser cached. Looking at the median repeat view run, we can see on line 2 this file from Typography.com is requested. This means it is not in the browser cache. Unfortunately, this leads to an extra 137ms of delay in the CSSOM construction. Fortunately, the redirect to the local font files is cached so the full 1.7s delay is not incurred.
Just to confirm that this result wasn’t an anomaly, I search for other sites using Typography.com and found similar patterns (halt.io, blog.optimizely.com, and chrisbowler.com). Results for these sites vary, but in each case, the start render is blocked by the font download. Interestingly, I tried Googling to see if this was a know fact about Typography.com and came to find that people only seemed to compare the load times of different font providers. Some suggested that Typography.com was better due to faster load times, but neglected the fact that it blocks the render completely. It just goes to show how important it is to not define web performance by a simple statistic or two.
I find this load process absolutely fascinating. Webfonts are really hard to load performantly. I think that Typography.com made what appeared to be smart choices (load CSS early, data URI encode fonts). These choices are popular choices for improving page load times; however, in this case, they backfired. By delaying the construction of the CSSOM, the start render performance is badly delayed.
In an attempt to be constructive, I would recommend the following:
- The CSS loaded from the local server should link to fonts instead of data URI encode them. I imagine there are some issues with this for Typography.com as they do not want to distribute those fonts so easily.
- The redirect should be browser cached.
- If first item cannot be satisfied, I think the initial request should be made via JS. This would lead to an async load of the CSS, which would not block the CSSOM construction. The end result would be similar to the Typekit experience.
Unfortunately, I am not in any position to confirm any of these assumptions. I would love to hear from anyone who interprets these waterfalls differently. I am also not in the position to test the difference in loading Typography.com fonts versus other fonts. I would expect nearly 1 second in reduced start render time if the font was swapped out for a Google Font.