No FOUT About It
There were some hard choices to make immediately. The first thing we discarded was webfonts, as these were bytes we simply didn’t have to spend.
font-family: -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",
"Segoe UI", "Helvetica Neue", sans-serif;
Discarding webfonts and instead using the system font on the device had three benefits for us.
First, it meant we didn’t have to worry about a flash-of-unstyled-text (FOUT). This happens when the browser renders the text before the font is loaded, and then renders it again after loading, resulting in a brief flash of text in the wrong style. Worse, the browser may block rendering any text at all until the font loads. These effects can be exaggerated by slow connections, and so being able to eliminate them completely was a major win.
Second, leveraging the system font meant that we were working with a large glyph set, a wide range of weights, and a typeface designed to look great on that device. Sure, customers on Android (which uses Roboto as the system font) would see a slightly different layout to customers on iPhone (San Francisco, or Helvetica Neue on older devices), or even customers on Windows Phone (Segoe UI). But, how often do customers switch between devices like that? For the most part, they will have a consistent experience and won’t realise that people on other devices see something slightly different.
Best of all, we got all of this at the cost of zero bytes from our page budget. System fonts were an absolute no-brainer, and I still use them today.
Going Framework Free
Jake Archibald once described the difference between a library and a framework like this: a library is something your code calls into, a framework is something which calls into your code.
Frontend web development has been dominated by frameworks at least since React, if not before. SproutCore, Cappuccino, Ember, and Angular all used a pattern where the framework controls the execution flow, and it hooks into your code as and when it needs to. Most of these would have broken our 128KB page budget before we had written a single line of application code.
We looked at libraries like Backbone, Knockout, and jQuery, but we knew we had to make every byte count. In the days before libraries were built for tree-shaking, almost any library we bundled would have included wasted bytes, so instead we created our own minimal library, named Whizz.
Whizz implemented just the API surface we needed: DOM querying, event handling, and AJAX requests. Much of it simply smoothed out browser differences, particularly important when supporting everything from IE8 to Safari 9 to Android Browser to Opera Mini. There was no virtual DOM, no complex state management, no heavy abstractions.
The design of Whizz was predicated on a simple observation: the header and footer of every page were the same, so re-fetching them when loading a new page was a waste of bytes. All we really needed to do was fetch the bits of the new page we didn’t already have.
We then handled updates with a very straightforward technique. An event listener would intercept the click, fetch the partial content via AJAX, and inject it into the page. (These were the days before the Fetch API, when we had to do everything with XMLHttpRequest. Whizz provided a thin wrapper around this.)
{
"title": "Document title for the new page",
"content": " element.
Browsers which supported would fetch the SVG, but browsers without support (or without SVG support) would fetch the PNG fallback instead. This was an improvement over JavaScript-based approaches, as we always fetched either PNG or SVG and never one then the other, as some polyfills might. We were fortunate too that none of those devices had HiDPI (aka Retina) screens, so we only needed to provide fallbacks at 1x scale.
The larger problem we had with SVG was one more unexpected, because it turned out that vector design tools like Adobe Illustrator and Inkscape produce really noisy, bloated SVGs. Adobe Illustrator especially embeds huge amounts of metadata into SVG exports, with unnecessarily precise coordinates for paths. This was compounded by artefacts resulting from the way graphic designers typically work in vector tools: hidden layers, deeply nested groups, redundant transforms, and sometimes even nested raster images. Literally, PNG or JPEG data embedded in the SVG, which you would never see unless you opened it in a code editor.
The result was images which should have been 500 bytes coming in at 5–10KB, or larger. If we were going to pull this off, we needed to very quickly become experts at SVG optimisation.
Optimising SVG: Part One
SVGO, the SVG optimisation tool, was relatively nascent at the time, but did a grand job of stripping away much of the Adobe cruft. Unfortunately, it wasn’t good enough on its own.
Many hours of experimentation took place, just fooling with the SVG code in an editor and seeing what that did to the image. We realised that we could strip out most of the grouping elements without changing anything. We could strip back most of the xmlns attributes on the
Partial HTML for just the new page
" } The AJAX request included a custom header, X-Whizz, which the server recognised as a Whizz request and returned just our JSON payload instead of the full page. Once injected into the page, we ran a quick hook to bind event listeners on any matching nodes in the new DOM. function onClick(event) { var mainContent; event.preventDefault(); mainContent = WHIZZ.querySelector("main"); WHIZZ.load(event.target.href, function (page) { document.title = page.title; WHIZZ.replaceContent(mainContent, page.content); WHIZZ.rebindEventListeners(mainContent); }); } This really cut down on the amount of data we were transferring, without needing heavy DOM manipulation, or fancy template engines running in the browser. Knitted together with a simple loading bar (just to give the user the feeling that stuff is moving along) it really made navigation, well, whizz! Imagine That Probably the most significant problem we faced in squeezing pages into such a tiny payload was images. Even a small raster image, like PNG or JPEG, consumes an enormous amount of bytes compared to text. Text content (HTML, CSS, JavaScript) also gzips well, typically halving the size on the wire, or more. Images, however, often don’t benefit from gzip compression. We had already committed to using them sparingly, but reducing the absolute number of images wasn’t enough on its own. While we started off using tools like OptiPNG to reduce our PNG images as part of the build process, during development we discovered TinyPNG (now Tinify). TinyPNG did a fantastic job of squeezing additional compression out of our PNG images, beyond what we could get with any other tool. Once we saw the results we were getting from TinyPNG, we quickly integrated it into our build process, and later made use of their API to recompress images uploaded by users. JPEG proved more of a challenge. These days Tinify supports JPEG images, but at the time they were PNG-only so we needed another approach. MozJPEG, a JPEG encoder tool from Mozilla, was pretty good and was a big improvement over the Adobe JPEG encoder we had been using. But we needed to push things even further. What we came up with involved exporting JPEGs at double the scale (so if we wanted a 100x100 image, we would export it 200x200) but taking the JPEG quality all the way down to zero. This typically produced a smaller file, albeit heavily artefacted. However, when rendered at the expected 100x100, the artefacts were not as noticeable. The left image is 100% scale, quality set to 0. The right image the same image at half size. The end result used more memory in the browser, but spared us precious bytes on the wire. We recognised this as a trade-off, and I’m still not 100% sure it was the best approach. But it was sparingly used, and effective for what we needed. The real wins came from embracing SVG. SVG has the advantage of being XML-based, so it compresses well and scales to any resolution. We could reuse the same SVG as the small and large versions of an icon, for example. Thankfully, it was also supported by Opera Mini. That isn’t to say SVG was all plain sailing. For one thing, not all of our target browsers supported it. Notably, Android Browser on Gingerbread did not have great SVG support, so our approach here was to provide a PNG fallback using the