Good Old-Fashioned Website Decay - Performance Optimization Roadmap - Responsive Web Design, Part 2 (2015)

Responsive Web Design, Part 2 (2015)

Performance Optimization Roadmap

BY VITALY FRIEDMAN

Improvement is a matter of steady, ongoing iteration. When we redesigned Smashing Magazine back in 2012, our main goal was to establish trustworthy branding to reflect the ambitious editorial direction of the magazine. We did that primarily by focusing on crafting a delightful reading experience. Over the years, our focus hasn’t changed a bit; however, the very asset that helped to establish our branding turned into a major performance bottleneck.

Good Old-Fashioned Website Decay

Looking back at the early days of our redesign, some of our decisions now seem to be quick-and-dirty fixes rather than sound long-term solutions. Our advertising constraints pushed us toward compromise. Legacy browsers drove us to depend on (relatively) heavy JavaScript libraries. Our technical infrastructure led us to heavily customized WordPress plugins and complex PHP logic. With every new feature we added, our technical debt grew, and our style sheets, markup and JavaScript weren’t getting any leaner.

Sound familiar? Admittedly, responsive web design as a technique often gets a pretty bad rap for bloating websites and making them difficult to maintain. (Not that non-responsive websites are any different, but that’s another story.) In practice, all assets on a responsive website will show up pretty much everywhere1, be it a slow smartphone, a quirky tablet or a fancy laptop with a Retina screen. And because media queries merely provide the ability to respond to screen dimensions — and do not, rather, have a more local, self-contained scope — adding a new feature and adjusting the reading experience potentially means going through each and every media query to prevent inconsistencies and fix layout issues.

“Mobile First” Means “Always Mobile First”

When it comes to setting priorities for the content and functionality on a website, “mobile first” is one of those difficult yet incredibly powerful constraints that help you focus on what really matters, and identify critical components of your website. We discovered that designing mobile first is one thing; building mobile first is an entirely different story. In our case, both the design and development phases were heavily mobile-first, which helped us focus tightly on the content and its presentation. But while the design process was quite straightforward, implementation proved to be quite difficult.

Because the entire website was built mobile first, we quickly realized that adding or changing components on the page would entail going through the mobile-first approach for every single (minor and major) design decision. We’d design a new component in a mobile view first, and then design an extended view for the situations when more space is available. Often that meant adjusting media queries with every single change, and more often it meant adding new stuff to style sheets and markup to address new issues that came up.

We found ourselves trapped: development and maintenance were taking a lot of time, the code base was full of minor and major fixes, and the infrastructure was becoming too slow. We ended up with a code base that had become bloated before the redesign was even released — very bloated2, in fact.

Performance Issues

In mid-2013, our home page weighed 1.4MB and produced 90 HTTP requests. It just wasn’t performing well. We wanted to create a remarkable reading experience on the website while avoiding the flash of unstyled text (FOUT), so web fonts were loaded in the <head> and, hence, blocked the rendering of content. (Actually, that’s correct behavior according to the spec3, designed to avoid multiple repaints and reflows.) jQuery was required for ads to be displayed, and a few JavaScripts depended on jQuery, so they were all blocking rendering as well. Ads were loaded and rendered before the content to ensure that they appeared as quickly as possible.

Images delivered by our ad partners were usually heavy and unoptimized, slowing down the page further. We also loaded Respond.js and Modernizr to deal with legacy browsers and to enhance the experience for smart browsers. As a result, articles were almost inaccessible on slow and unstable networks, and the rendering start time on mobile was disappointing at best.

It wasn’t just the front-end that was showing its age though. The back-end wasn’t getting any better either. In 2012 we were playing with the idea of having fully independent sections of the magazine — sections that would live their own lives, evolving and growing over time as independent WordPress installations, with custom features and content types that wouldn’t necessarily be shared across all sections.

Because WordPress multi-install wasn’t available at the time, we ended up with six independent, autonomous WordPress installations with six independent, autonomous style sheets. Those installs were connected to 6×2 databases (a media server and a static content server). We ran into dilemmas. For example, what if an author wrote for two sections and we wanted to show their articles from both sections on one single author’s bio page? Well, we’d need to somehow pull articles from both installations and add redirects for each author’s page to that one unified page; or should we just use one of those pages as a kind of host? Well, you know where this is going: increasing complexity and increasing maintenance costs. In the end, individual sections didn’t manage to evolve significantly — at least not in terms of content — yet we had already customized the technical foundation of each section, adding to the CSS dust and PHP complexity.

(Because we had outsourced WordPress tasks, some plugins depended on each other. If we were to deactivate one, we might have unwittingly disabled two or three others in the process, and they would have to be turned back on in a particular order to work properly. There were even differences in the HTML output by the PHP templates behind the scenes, such as classes and IDs that differed from one installation to the next. It’s no surprise that this setup made development a bit frustrating.)

Traffic stagnated, readers kept complaining about the performance of the site, and only a very small portion of users visited more than two pages per visit. The visual feedback when browsing the site was visible and surely wasn’t instant, and this lag had been driving readers away from the site to Instapaper and Pocket — both on mobile and desktop. We knew that because we asked our readers, and the feedback was quite clear (and frustrating again).

It was time to push back, heavily, with a major refactoring of the code base. We looked carefully under the hood, discovering a few pretty scary (and nasty) things, and started fixing issues, one by one. It took us quite a bit of time to make things right, and we learned quite a few things along the way.

Switching Gears

Up until mid-2013, we weren’t using a CSS preprocessor, nor any build tools. Good long-term solutions require a good long-term foundation, so the first issues we tackled were tooling and the way the code base was organized. Because a number of people had worked on the code base over the years, some things proved to be rather mysterious… or challenging, to say the least.

We started with a code inventory, and we looked thoroughly at every single class, ID and CSS selector. Of course, we wanted to build a system of modular components, so the first task was to turn our seven large CSS files into maintainable, well-documented and easy-to-read modules. At the time, we’d chosen LESS, for no particular reason, and so our front-end engineer Marco started to rewrite CSS and build a modular, scalable architecture. Of course, we could very well have used Sass instead, but Marco felt quite comfortable with LESS at that point.

With a new CSS architecture, Grunt4 as a build tool and a few time-saving Grunt tasks5, the job of maintaining the entire code base became much easier. We set up a brand-new testing environment, synced up everything with GitHub, assigned roles and permissions, and started digging. We rewrote selectors, reauthored markup, and refactored and optimized JavaScript. And yes, it took us quite some time to get things in order, but it really wouldn’t have been so difficult if we hadn’t had a number of very different style sheets to deal with.

The Big Back-End Cleanup

With the introduction of Multisite, creating a single WordPress installation from our six separate installations became a necessary task for our friends at Inpsyde6. Over the course of five months, Christian Brückner and Thomas Herzog cleaned up the PHP templates, kicked unnecessary plugins into orbit, rewrote plugins we had to keep, and added new ones where needed. They cleared the databases of all the clutter that the old plugins had created — one of the databases weighed in at 70GB (no, that’s not a typo — we do mean gigabytes) — merged all of the databases into one, and then created a single fresh and, most importantly, maintainable WordPress multisite installation.

The speed boost from these optimizations was remarkable: about 400 to 500 milliseconds of improvement by avoiding subdomain redirects, and unifying the code base and the back-end code. Redirects7 are indeed a major performance culprit, and just avoiding them is one of those techniques that usually boost performance significantly because you avoid full DNS lookups, improve time to first byte and reduce round trips on the network.

Thomas and Christian also refactored our entire WordPress theme according to the coding standard of their own theme architecture, which is basically a sophisticated way of writing PHP based on the WordPress standard. They wrote custom drop-ins that we use to display content at certain points in the layout. Writing the PHP strictly according to WordPress’ official API felt like getting out of a horse-drawn carriage and into a racing car. All modifications were done without ever touching WordPress’s core, which is wonderful because we’ll never have to fear updating WordPress itself anymore.

Spam comments
We also marked a few million spam comments across all the sections of the magazine. And before you ask: no, we did not import them into the new install.

We migrated the installations during a slow weekend in April 2014. It was a huge undertaking, and our server had a few hiccups during the process. We brought together over 2,500 articles, including about 15,000 images, all spread over six databases, which also exhibited some major inconsistencies. While it was a very rough start at first — a lot of redirects had to be set up, caching issues on our server piled up, and some articles got lost between the old and new installations — the result was well worth the effort.

Our editorial team, primarily Iris, Melanie and Markus, worked very hard to bring those lost articles back to life by analyzing our 404s with Google Webmaster Tools. We spent a few weekends ensuring that every single article was recovered and remains accessible. Losing articles, including their comments, was simply unacceptable.

We know very well how much time it takes for a good article to get published, and we have a lot of respect for authors and their work, and making sure the content remains online was a matter of respect for the work published. It took us a few weeks to get there and it wasn’t the most enjoyable experience for sure, but we used the opportunity to introduce more consistency in our information architecture and adjust tags and categories appropriately. (Of course, if you do happen to find an article that has gotten lost along the way, please do let us know and we’ll fix it right away. Thanks!)

Front-End Optimization

In April 2014, once the new system was in place and had been running smoothly for a few days, we rewrote the LESS files based on what was left of all of the installs. Streamlining the classes for posts and pages, getting rid of all unneeded IDs, shortening selectors by lowering their specificity, and rooting out anything in the CSS we could live without crunched the CSS from 91KB down to a mere 45KB.

Once the CSS code base was in proper shape, it was time to reconsider how assets were loaded on the page and how we could improve the start rendering time beyond having clean, well-structured code. Given the nightmare we had experienced with the back-end, you might assume that improving performance at this point would have been a complex, time-consuming task, but actually it was quite a bit easier than that. Basically, it was just a matter of getting our priorities right by optimizing the critical rendering path.

The key to improving performance was to focus on what mattered most: the content, and the fastest way for readers to actually start reading our articles on their devices. Over a course of a few months we kept reprioritizing. With every update, we introduced mini-optimizations based on a very simple, almost obvious principle: optimize the delivery of content, and defer the rest — without any compromise, anywhere.

Our optimizations were heavily influenced by the work done by Scott Jehl8, as well as the Guardian newspaper9 and the BBC10 teams (both of which open-sourced their work). While Scott had been sharing valuable insight11 into the front-end techniques that Filament Group was using, the BBC and the Guardian helped us to define and refine the concept of the core experience on the website and use it as a baseline. A shared main goal was to deliver the content as fast as possible to as many people as possible regardless of their device or network capabilities, and enhance the experience with progressive enhancement for capable browsers.

Historically we haven’t had a lot of JavaScript or complex interactions on Smashing Magazine, so we didn’t feel that it was necessary to introduce complex loading logic with JavaScript preloaders. However, being a content-focused website, we did want to reduce the time necessary for the articles to start displaying as far as technically possible.

Performance Budget: Speed Index <= 1,000

How fast is fast enough?12 Well, that’s a tough question to answer. In general, it’s quite difficult to visualize performance and explain why every millisecond counts — unless you have hard data. At the same time, falling into the trap of absolutes and relying on not truly useful performance metrics is easy. In the past, the most commonly cited performance metric was average loading time. Yet, on its own, average loading time isn’t that helpful because it doesn’t tell you much about when a user can actually start using the website. This is why talking about “fast enough” is often so tricky.

Comparing Progress
A nice way of visualizing performance is to use WebPagetest to generate a video of the page loading and run a test between two competing websites. In addition, the speed index metric often proves very useful.

Different components require different amounts of time to load, yet some components of the page are more important than others. For instance, you don’t need to load the footer content quickly, but it’s a good idea to render the visible portion of the page as soon as possible. As Ilya Grigorik once said13, “We don’t need to render the entire page in one second, we need to render the above the fold content.” To achieve that, according to Scott’s research and Google’s test results, it’s helpful to set ambitious performance goals:

•On WebPagetest14, aim for a speed index15 value of under 1,000.

•Ensure that all HTML, CSS and JavaScript fit within the first 14KB.

What do they mean and why are they important? According to human-computer interaction (HCI) research, “[f]or an application to feel instant, a perceptible response to user input must be provided within hundreds of milliseconds. After a second or more, the user’s flow and engagement with the initiated task is broken.” With the first goal, we are trying to ensure an instant response from our website. It refers to the speed index metric for the start rendering time — the average time (in milliseconds) at which visible parts of the page are displayed, or become accessible. The first goal basically states that a page should start rendering under 1,000ms; and yes, it’s a quite difficult challenge to take on.

Browser Networking
Ilya Grigorik’s book High Performance Browser Networking is a very helpful guide with useful advice on making websites fast. And it’s available as a free HTML book16, too.

The second goal can help to achieve the first. The value of 14KB has been measured empirically17 by Google and is the threshold for the first package exchanged between a server and client via towers on a cellular connection. You don’t need to include images within 14KB, but you might want to deliver the markup, style sheets and any JavaScript required to render the visible portion of the page within that threshold. Of course, in practice this value can only realistically be achieved with gzip compression.

By combining the two goals, we basically defined a performance budget for the Smashing Magazine website — a threshold for what was acceptable. Admittedly, we didn’t concern ourselves with the start rendering time on different devices on various networks, mainly because we really wanted to push back as far as possible everything that wasn’t required to start rendering the page. So, the ideal result would have been a speed index value that was much lower than the one we had set — as low as possible, actually — in all settings and on all connections, both shaky and stable, slow and fast. This might sound naive, but we wanted to figure out how fast we could be, rather than how fast we should be. We did measure start rendering time for first and subsequent page loads, but we did that much later, after optimizations had already been done, and only to keep track of issues on the front-end.

Our next step would be to integrate Tim Kadlec’s perfbudget Grunt task18 to incorporate the performance budget right into the build process and thereby run every new commit against WebPagetest’s performance benchmark. If it failed, we knew that a new feature had slowed us down, so we probably had to reconsider how it was implemented to fit it within our budget, or at least we knew where we stood and could have meaningful discussions about its impact on the overall performance.

Prioritization and Separation of Concerns

If you’ve been following the Guardian’s work recently, you might be familiar with the strict separation of concerns19 that it introduced during the major 2013 redesign. The Guardian separated all its content20 into three main groups:

Core content: essential HTML and CSS, usable non-JavaScript-enhanced experience.

Enhancement: JavaScript, geolocation, touch support, enhanced CSS, web fonts, images, widgets.

Leftovers: Analytics, advertising, third-party content.

Browser Networking
A strict separation of concerns, or loading priorities, as defined by The Guardian team.

Once you have defined, confirmed and agreed on these priorities, you can push performance optimization quite far. Just by being very specific about each type of content you have and by clearly defining what core content is, you are able to load core content as quickly as possible; then load enhancements once the page starts rendering (after the DOMContentLoaded event fires); and then load leftovers after the page has fully rendered (after the load event fires).

The main principle here, of course, is to strictly separate the loading of assets throughout these three phases, so that loading of core content should never be blocked by any resources grouped in enhancement or leftovers (we haven’t achieved the perfect separation just yet, but we’re on it). In other words, you try to shorten the critical rendering path that is required for the content to start displaying by pushing the content down the line as fast as possible and deferring pretty much everything else.

We followed this same separation of concerns, grouping our content types into the same categories and identifying what’s critical, what’s important and what’s secondary. In our case, we identified and separated content in this way:

Core content: only essential HTML and CSS.

Enhancement: JavaScript, code syntax highlighter, full CSS, web fonts, comment ratings.

Leftovers: analytics, advertising, Gravatars.

Once you have this simple content/functionality priority list, improving performance becomes just a matter of adding a few snippets for loading assets to properly reflect those priorities. Even if your server logic forces you to load all assets on all devices, by focusing on content delivery first, you ensure that the content is accessible quickly, while everything else is deferred and loaded in the background, after the page has started rendering. From a strategic perspective, the list also reflects your technical debt, as well as critical issues that slow you down. Indeed, we had quite a list of issues to deal with already at this point, so it transformed fairly quickly into a list of content priorities. And a rather tricky issue sat right at the top of that list: web fonts.

Deferring Web Fonts

Despite the fact that the proportion of Smashing Magazine’s readers on mobile has always been quite modest (around 15%, mainly owing to the length of articles), we never considered mobile as an afterthought — but we never pushed user experience on mobile either. And when we talked about user experience on mobile, we mostly talked about speed, since typography was pretty much well designed from day one.

We had conversations during the 2012 redesign about how to deal with fonts, but we couldn’t find a solution that made everybody happy. The visual appearance of content was important, and because the new Smashing Magazine was all about beautiful, rich typography, not loading web fonts at all on mobile wasn’t really an option.

With the redesign back then, we switched to Skolar for headings and Proxima Nova for body copy, delivered by Fontdeck. Overall, we had three fonts for each typeface (regular, italic and bold) totalling six font files to be delivered over the network. Even after our dear friends at Fontdeck subsetted and optimized the fonts, the assets were quite heavy with over 300KB in total. Because we wanted to avoid the flash of unstyled text, we had them loaded in the <head> of every page. Initially, we thought the fonts would reliably be cached in HTTP cache, so they wouldn’t be retrieved with every single page load. Yet it turned out that HTTP cache was quite unreliable: the fonts showed up in the waterfall loading chart every now and again for no apparent reason, both on desktop and mobile.

The biggest problem, of course, was that the fonts were blocking rendering21. Even if the HTML, CSS and JavaScript had already loaded completely, the content wouldn’t appear until the fonts had loaded and rendered. A visitor had this beautiful experience of seeing link underlines first, then a few keywords in bold here and there, then subheadings in the middle of the page and then, finally, the rest of the page. In some cases, when Fontdeck had server issues, the content didn’t appear at all, even though it was already sitting in the DOM, waiting to be displayed.

LP font by Ian Feather
In his article, “Web Fonts and the Critical Path”, Ian Feather provides a very detailed overview of the FOUT issues and font loading solutions. We tested them all.

We experimented with a few solutions before settling on what turned out to be perhaps the most difficult one. At first, we looked into using Typekit and Google’s Web Font Loader22, an asynchronous script which gives you more granular control of what appears on the page while fonts are being loaded. The script adds a few classes to the <body> element, which allows you to specify the styling of content in CSS during loading and after the fonts have loaded. You can be very precise about how the content is displayed in fallback fonts first, before users see the switch from fallback fonts to web fonts.

We added fallback font declarations and ended up with pretty verbose CSS font stacks, using iOS fonts, Android fonts, Windows Phone fonts, and good ol’ web-safe fonts as fallbacks — we still use these font stacks today. For example, we used this cascade for the main headings (it reflects the order of popularity of mobile operating systems in our analytics):

h2 {

font-family: "Skolar Bold",

AvenirNext-Bold, "Avenir Bold",

"Roboto Slab", "Droid Serif",

"Segoe UI Bold",

Georgia, "Times New Roman", Times, serif;

}

Readers would see a mobile OS font (or any other fallback font first), and it would probably be a font that they were quite familiar with on their device. Once the fonts had loaded, they would see a switch, triggered by Web Font Loader. However, we discovered that after switching to Web Font Loader, we started seeing FOUT far too often, with HTTP cache becoming quite unreliable again; the permanent switch from a fallback font to the web font was quite annoying, ruining the reading experience.

So we looked for alternatives. One solution was to include the @font-face directive only on larger screens by wrapping it in a media query, thereby avoiding loading web fonts on mobile devices and in legacy browsers altogether. (In fact, if you declare web fonts in a media query, they will be loaded only when the media query matches the screen size — no performance hit there.) Obviously, it helped us improve performance on mobile devices in no time, but we didn’t feel right about having a simplified reading experience on mobile devices. So it was a no-go, too.

What else could we do? The only other option was to improve font caching. We couldn’t do much with HTTP cache, but there was one possibility we hadn’t looked into: storing fonts in AppCache or localStorage. Jake Archibald’s article on the beautiful complexity of AppCache23 led us away from AppCache to experiment with localStorage, a technique24 that the Guardian’s team was using at the time.

Now, offline caching comes with one major requirement: you need to have the actual font files to be able to cache them locally in the client’s browser. And you can’t cache a lot because localStorage space is very limited25, sometimes with just 5MB available per domain. Luckily, the Fontdeck guys were very helpful and forthcoming with our undertaking; despite the fact that font delivery services usually require you to load files and have a synchronous or asynchronous callback to count the number of impressions, Fontdeck has been perfectly fine with us grabbing WOFF files from Google Chrome’s cache and setting up a flat pricing structure based on the number of page impressions in recent history.

We grabbed the WOFF files and embedded them, base64-encoded, in a single CSS file, moving from six external HTTP requests with about 50KB each, to at most one HTTP request on the first load and 400KB of CSS. Obviously, we didn’t want this file to be loaded on every visit, so if localStorage is available on the user’s machine, we store the entire CSS file in localStorage, set a cookie, and switch from the fallback font to the web font. This switch usually happens once at most because for subsequent visits we check whether the cookie has been set and, if so, retrieve the fonts from localStorage (causing about 50ms in latency) and display the content in the web font right away. Just before you ask: yes, read/write to localStorage is much slower than retrieving files from HTTP cache26, but it proved to be a bit more reliable in our case.

Browserscope Graph
Yes, localStorage is much slower than HTTP cache, but it’s more reliable. Storing fonts in localStorage isn’t the perfect solution, but it helped us improve performance dramatically.

If the browser doesn’t support localStorage, we include fonts with <link href=""> and, well, frankly just hope for the best — that the fonts will be properly cached and persist in the user’s browser cache. For browsers that don’t support WOFF27 (IE8, Opera Mini, Android <= 4.3), we provide external URLs to fonts with older font mime types, hosted on Fontdeck.

Now, if localStorage is available, we still don’t want it to block the rendering of the content. And we don’t want to see FOUT every single time a user loads the page. That’s why we have a little JavaScript snippet in the header before the <body> element: it checks whether a cookie has been set and, if not, we load web fonts asynchronously after the page has started rendering. Of course, we could have avoided the switch by just storing the fonts in localStorage on the first visit and have no switch during the first visit, but we decided that one switch is acceptable, because our typography is important to our identity.

The script was written, tested and documented by our good friend Horia Dragomir. Of course, it’s available as a gist on GitHub28:

<script type="text/javascript">

(function () {

"use strict";

// once cached, the css file is stored on the client forever unless

// the URL below is changed. Any change will invalidate the cache

var css_href = './web-fonts.css';

// a simple event handler wrapper

function on(el, ev, callback) {

if (el.addEventListener) {

el.addEventListener(ev, callback, false);

} else if (el.attachEvent) {

el.attachEvent("on" + ev, callback);

}

}

// if we have the fonts in localStorage or if we’ve cached them using the native browser cache

if ((window.localStorage && localStorage.font_css_cache) || document.cookie.indexOf('font_css_cache') > -1){

// just use the cached version

injectFontsStylesheet();

} else {

// otherwise, don’t block the loading of the page; wait until it’s done.

on(window, "load", injectFontsStylesheet);

}

// quick way to determine whether a css file has been cached locally

function fileIsCached(href) {

return window.localStorage && localStorage.font_css_cache && (localStorage.font_css_cache_file === href);

}

// time to get the actual css file

function injectFontsStylesheet() {

// if this is an older browser

if (!window.localStorage || !window.XMLHttpRequest) {

var stylesheet = document.createElement('link');

stylesheet.href = css_href;

stylesheet.rel = 'stylesheet';

stylesheet.type = 'text/css';

document.getElementsByTagName('head')[0].appendChild(stylesheet);

// just use the native browser cache

// this requires a good expires header on the server

document.cookie = "font_css_cache";

// if this isn’t an old browser

} else {

// use the cached version if we already have it

if (fileIsCached(css_href)) {

injectRawStyle(localStorage.font_css_cache);

// otherwise, load it with ajax

} else {

var xhr = new XMLHttpRequest();

xhr.open("GET", css_href, true);

on(xhr, 'load', function () {

if (xhr.readyState === 4) {

// once we have the content, quickly inject the css rules

injectRawStyle(xhr.responseText);

// and cache the text content for further use

// notice that this overwrites anything that might have already been previously cached

localStorage.font_css_cache = xhr.responseText;

localStorage.font_css_cache_file = css_href;

}

});

xhr.send();

}

}

}

// this is the simple utitily that injects the cached or loaded css text

function injectRawStyle(text) {

var style = document.createElement('style');

style.innerHTML = text;

document.getElementsByTagName('head')[0].appendChild(style);

}

}());

</script>

During testing of this technique, we discovered a few surprising problems. Because the cache’s persistence varies in WebViews, sometimes fonts do load asynchronously in applications such as TweetDeck and Facebook; yet they don’t remain in the cache once the application is closed (although they remain in the cache while the application is running!). localStorage isn’t shared between the apps and the Safari browser. In other words, in the worst case, with every WebViews visit — let’s say from a Facebook app or a Twitter app — the fonts will be redownloaded and rerendered. Some old BlackBerry devices seem to clear cookies and delete the cache when the battery is running out. And depending on the configuration of the device, sometimes fonts might not persist in localStorage either.

Still, once the snippet was in place, articles started rendering much faster. By deferring the loading of web fonts and storing them in localStorage, we’ve prevented around 700ms delay, and thus shortened the critical path significantly by avoiding the latency for retrieving all the fonts. The result was quite impressive for the first load of an uncached page, and it was even more impressive for concurrent visits since we were able to reduce the latency caused by web fonts to just 40–50ms. In fact, if we had to highlight just one improvement to performance on the website, deferring web fonts is by far the most effective.

This isn’t a definitive solution though. In many ways, the technique feels like a heavy workaround. For example, it would be smarter to detect whether the browser supports the new WOFF2 format29 (currently supported by Chrome and Opera), WOFF (most modern browsers, IE9+), or TrueType (Android 4.1–4.3 support) and store corresponding base64-encoded font files in the browser’s cache, rather than serving and caching primarily WOFF and loading other font formats on demand. In fact, that’s what Filament Group have been doing recently30: using a style sheet loader, a WOFF2/TrueType feature test and a cookie utility to load, cache and later access only supported font files from HTTP cache.

This is also an approach we are considering at the moment. In our case we would provide six font variants in three file formats, generating eighteen CSS files with base64-encoded web fonts, that would then be stored either in localStorage or in HTTP cache with a cookie set. A move to WOFF2 might be very much worth it, since the file format promises a better compression for font files and it has already shown remarkable results. In fact, the Guardian was able to cut down on 200ms latency and 50KB of the file weight31 by switching to WOFF2.

Of course, grabbing WOFFs might not always be an option for you, but it wouldn’t hurt just to talk to type foundries to see where you stand, or to work out a deal to host fonts locally. Otherwise, tweaking Web Font Loader for Typekit and Fontdeck is definitely worth considering. These techniques work well today, and they can significantly boost the performance of your website. In the long run, the Font Load Events API will (hopefully) replace them: think of it as the Web Font Loader library, but implemented natively in browsers. As described in Bram’s chapter on web font performance, you can construct a FontFace object directly in JavaScript, specify how and when the font will be rendered, trigger an immediate fetch of the font files, and avoid blocking on CSSOM and DOM entirely. The API is already supported in Chrome and Firefox, and it’s a solution that is very likely to solve most web font performance issues for good.

Dealing With JavaScript

With the goal of removing all unnecessary assets from the critical rendering path, the second target we decided to deal with was JavaScript. It’s not as though we particularly dislike JavaScript for some reason, but we always tend to prefer non-JavaScript solutions to JavaScript ones. In fact, if we can avoid JavaScript or replace it with CSS, then we’ll always explore that option.

Back in 2012, we weren’t using a lot of scripts on the page, yet displaying advertising via OpenX depended on jQuery, which made it way too easy to lazily approach simple, straightforward tasks with ready-to-use jQuery plugins. At the time, we also used Respond.js to emulate responsive behaviour in legacy browsers. However, Internet Explorer 8 usage has dropped significantly between 2012 and 2014: at 4.7% before the redesign, it was now 1.43%, with a tendency to drop every single month. So we decided to deliver a fixed-width layout with a specific IE8 style sheet to those users, and removed Respond.js altogether.

As a strategic decision, we decided to defer loading all JavaScript until the page had started rendering, and we looked into replacing jQuery with lightweight modular JavaScript components.

jQuery was tightly bound to ads, and ads were supposed to start displaying as fast as possible; to make it happen, we had to deal with advertising first. The decision to defer the loading of ads wasn’t easy to get agreement on, but we managed to make a convincing argument that better performance would increase click rates because users would see the content sooner. That is, on every page, readers would be attracted by the high-quality content and then, when the ads kick in, would pay attention to those squares in the sidebar as well.

Florian Sander, our partner in crime when it comes to advertising, rewrote the script for our banner ads so that banners would be loaded only after the content has started rendering, and only then the advertising spots would be put into place. Florian was able to get rid of two render-blocking HTTP requests that the ad script normally generated, and we were able to remove the dependency on jQuery by rewriting the script in vanilla JavaScript.

Obviously, because the sidebar’s ad content is generated on the fly and is loaded after the render tree has been constructed, we started seeing reflows (this still happens when the page is being constructed). Because we used to load ads before the content, the entire page (with pretty much everything) used to load at once. Now, we’ve moved to a more modular structure, grouping together particular parts of the page and queuing them to load after one another. This has made the overall experience on the site a bit noisier because there are a few jumps here and there: in the sidebar, in the comments and in the footer. That was a compromise we accepted, and we’ve been working on a solution to reserve space for jumping elements to avoid reflows as the page is being loaded.