Adventures of a beginner in Web Performance

Wade Dominic
15 min readMay 19, 2017

I’ve had a big interest in web performance for quite some time which is the reason I have spent probably the better half of a year learning about static site generators and implementing my own in Clojure and Clojurescript. Currently I host all my static sites on Netlify. They are hands down the best solution out there for Static Site Deployment unless you have specific needs or wish to roll your own. Which is also perfectly within reach if you are willing to put in the work.

I’ve recently had a web project I have been working on which gave me a reason to really dive in and see how fast I can get the website to go.

In the following I will talk about

  • Inlining CSS
  • Asynchronously loading non-critical styles
  • Asynchronously loading fonts and JavaScript
  • Deferring non-critical JS
  • Using inlined SVG sprites and Data URI

Inlining CSS

If you haven’t heard already Inlining CSS in a style tag within the head of the document is now a thing. This is usually done with a tool such as Critical or CriticalCSS . These tools allow you to extract just the styles that will be seen when the human first visits your website. The reason for this is because of the way websites are delivered. Your website is sent in small packets starting at 15kb and increasing in size the longer the connection is held. This means for the absolute best speed you need to work on showing your visitor something in that first 15kb packet. HTML is also progressively downloaded and rendered meaning you don’t have to wait for the entire file to download. You can show em what you got!

There is a few problems that arise with this strategy.

  1. The styles are not cached and shared across other pages. Which means depending on how your website is built you will have to keep those styles in the asynchronously loaded CSS file or files
  2. When you change the HTML people will have to download all the inlined CSS again even if the styles never changed
  3. The Block Element Modifier (BEM) methodology gets in the way as it impacts the bottom line of reaching that 15kb target

The first problem is a tricky one to solve regardless as it is one of modularity. However the best way to solve the first and second problem is putting the Critical CSS into a separate file and server pushing it with the new HTTP/2 spec. This is only just now starting to be provided with a number of hosting solutions however if you are serving a completely static site from a CDN you have even less options.

The last problem is one that for the most part is being solved in various ways. The use of Atomic (single purpose) styles has long been proposed as the best solution but in practice was difficult apply until now, mostly due to there being a real lack of mental models or tools for doing it.

Tachyons provides a great framework for those that prefer using plain CSS.

There is also higher level frameworks being built on top of Tachyons (such as Purple3) to provide more real structure which a lot of people using CSS frameworks such as Bootstrap are used to. I personally fall into the category that likes the CSS-in-JS style solutions that also render server side. For some examples of those check out Styletron and Fela. Since I am in the land of Clojure I’m working on having a similar workflow using the Garden library. I’m not a fan of having to remember shorthand order in CSS let alone remember shorthand classnames and acronyms that Tachyons would make me do (though there is verbose modes in the JS ecosystem with the build tool plugins). I should probably also mention that the atomic nature of these styles also eliminates a lot of accidental overrides of other styles, makes the styles much more composable and removes the need for descendant selectors which is more time consuming for the browser to figure out.

For learning more about CSS delivery and making your styles super fast I definitely suggest checking out this video.

Asynchronously loading non-critical styles

With the non-critical styles we still want to download them straight away but we don’t want it to be render blocking (stopping the rest of the html from downloading). We now have a new Preload directive for HTML this tells the browser. Hey! Go download this ASAP! But keep doing other work. I read this fantastic article on Smashing Magazine titled Preload: What Is It Good For?. Yoav Weiss lays it all out on why you should use preload and it was practically a copy and paste to get my site going faster.

<link rel="preload" as="style" href="async_style.css" onload="this.rel='stylesheet'">

This works perfectly. But browser support for preload as of 19 May 2017 isn’t actually that good..

Browser Support for Preload

So I had to do a little work. An example feature detection script is provided towards the bottom of that article but it was a very fill in the blanks solution. However it was a great start.

var DOMTokenListSupports = function(tokenList, token) { 
if (!tokenList || !tokenList.supports) {
return;
} try {
return tokenList.supports(token);
} catch (e) {
if (e instanceof TypeError)
{ console.log(“The DOMTokenList doesn’t have a supported tokens list”);
} else { console.error(“That shouldn’t have happened”); }
}
}
var linkSupportsPreload = DOMTokenListSupports(document.createElement(“link”).relList, “preload”);
if (!linkSupportsPreload)
{
// Dynamically load the things that relied on preload.
}

I needed to dynamically change the preload directive when it wasn’t found.. essentially create my own little polyfill.

var mylinks = document.querySelectorAll(“link[rel=preload]”);
for (var i = 0, l = mylinks.length; i < l; i++) {
mylink = mylinks[i];
if (mylink.getAttribute(“as”) == “font”) {
mylink.removeChild();
} else {
mylink.rel=”stylesheet”;
}
}

What I am doing here is getting all of the nodes which has a

<link rel=“preload”>

I then iterate over these links to change them. Just doing this wholesale though would throw an error when preloading my font. So I also check the as attribute and remove it when it is set to “font”. This could be easily done for other types of resources. Otherwise I just change the rel to stylesheet as it would have done when downloading on a browser that supports preload. I could use something like LoadCSS but I never actually ended up using an external stylesheet on this project and since it’s at the bottom the impact isn’t as critical as being in the head.

So we can dust off our hands and call it a day. Not quite!

Browser Support for relList (DOMTokenList)

There is a few problems.

For some reason I was getting undefined when querying this on Chrome and Opera the two browsers where the preload actually worked….

Safari
Firefox
Chrome

So now the browsers that were working before aren’t anymore.

(!tokenList || !tokenList.supports)

Would return true on undefined.

Sigh..

I added in an undefined check in the if statement

(!linkSupportsPreload && linkSupportsPreload != undefined)

Looking at this today as I write this blog post the browsers aren’t showing this anymore. I have no idea what was going on. Anyways the second problem is that relList actually hasn’t got very good browser support. So instead of just returning nothing I found a polyfill for the DOMTokenList API and recursively call the DOMTokenListSupports function so I could use the simple polyfill I created.

The end result was this.

<script>
var DOMTokenListSupports = function(tokenList, token) {
if (!tokenList || !tokenList.supports) {
var script = document.createElement("script");
script.src = "/js/domtokenlist.min.js";
script.async = true;
script.onload = function() {
DOMTokenListSupports ;
};
document.head.appendChild(script);
} else {
return tokenList.supports(token);
};
};
var linkSupportsPreload = DOMTokenListSupports(document.createElement("link").relList, "preload");if (!linkSupportsPreload && linkSupportsPreload != undefined) {
var mylinks = document.querySelectorAll("link[rel=preload]");
for (var i = 0, l = mylinks.length; i < l; i++) {
mylink = mylinks[i];
if (mylink.getAttribute("as") == "font") {
mylink.removeChild();
} else {
mylink.rel="stylesheet"
};
};
}
</script>

I already touched on a little bit of what I did with my fonts when the preload directive wasn’t available. But now I will explain what I did when it was working.

<link rel=”preload” href=”/fonts/josefin-sans.woff2" as=”font” type=”font/woff2" crossorigin>

The same way I did a copy and paste from the Smashing Magazine article I did it for my font. The font was however a little bit more trickier just because of the way browser handles fonts. Asynchronous fonts will get a heck of a lot easier in the future as you will just be able to use the

font-display:swap; 

property:value in your @font-face declaration. Which will automatically use fallback fonts and swap in the downloaded fonts when they are ready. Till then we have to use JavaScript to handle our font loading. I found this great write up explaining how to do async font loading. Which was essentially a copy and paste solution.

var html = document.documentElement; if (sessionStorage.fontsLoaded) { 
html.classList.add(“fonts-loaded”);
} else {
var script = document.createElement(“script”);
script.src = “/path/to/fontfaceobserver.js”;
script.async = true;
script.onload = function () {
var regular = new FontFaceObserver(“Source Serif”);
var bold = new FontFaceObserver(“Source Serif”,
{ weight: “bold” });
Promise.all([
regular.load(),
bold.load()
]).then(function () {
html.classList.add(“fonts-loaded”);
sessionStorage.fontsLoaded = true;
});
};
document.head.appendChild(script);
}

This script checks to see if the font is already in sessionStorage which is a way of preventing the flash of unstyled text when we have the font already in the cache.

This seems to work well on Wifi but when throttling it to 3g it does a Flash Of Invisible Text (FOIT) for quite some time.Not sure why that is.

If it isn’t in the sessionStorage then it downloads the fontfaceobserver.js file which you can get from Github.

I didn’t want to create a new node. I actually downloaded the JavaScript file earlier using.

<link href=”/js/fontfaceobserver.js” async>

Couldn’t I just reuse that I thought? Hmm I wonder if there is a difference in Performance. After all that’s why React and Virtual DOM’s are such a big deal. Just a quick google search later I found out how to check. https://developer.mozilla.org/en-US/docs/Web/API/Performance/now.

So I checked

var t0 = performance.now();
var script = document.createElement(“script”);
script.src = “/js/fontfaceobserver.js”;
script.async = true;
document.head.appendChild(script);
var t1 = performance.now();
console.log(“Call to createElement took “ + (t1 — t0) + “ milliseconds.”);

The result was: 0.6 milliseconds

var t0 = performance.now();
var script = document.createElement("script");
document.querySelector("[src='/js/fontfaceobserver.js']");
var t1 = performance.now();
console.log("Call to querySelector took " + (t1 - t0) + " milliseconds.");

The result was: 3.6 milliseconds

Ok. So kind of yucky to keep a random element in my html that I’m not using… I like to keep my code as clean as possible. But it is way faster to just create a new one and append it to the head so I’ll do that.

Deferring non-critical JavaScript

I’m actually using Rum which is nice Clojurescript wrapper over React. I love it as it I find it much easier to read and write than React code, it also doesn’t impose any way to handle your state unlike most of the other Clojurescript wrappers available on React. Because it is Clojurescript my code is also ran through the Closure Compiler from Google meaning the final Javascript file comes out really small.

Anyway back on topic.

This project had a video background with very little need for Javascript straight away, so I decided I wanted to defer the Javascript. The Defer directive however doesn’t actually defer it the way we may think and it can actually impact your ranks in the Page Speed results.

The defer refers to the execution of the scripts. It still downloads it straight away asynchronously. With fairly high priority from the browsers. To learn more about the differences this is a nice short article that explains it.

http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html.

So I dug around the web a bit more and found this article https://varvy.com/pagespeed/defer-loading-javascript.html. I did a straight copy and paste and added in the name of my file.

<script type="text/javascript">
function downloadJSAtOnload() {
var element = document.createElement("script");
element.src = "app.js";
document.body.appendChild(element);
}
if (window.addEventListener)
window.addEventListener("load", downloadJSAtOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", downloadJSAtOnload);
else window.onload = downloadJSAtOnload;
</script>

Dusted off my hands and called it a day.

Using Inline SVG Sprites

This was probably the most challenging thing to do. I fortunately wasn’t around in the days when you had to sprite images using actual positioning. So my frustrations are probably much smaller than yours might have been. But I really feel that SVG needs some more documentation around it as I really struggled to land on the right solution. I read a number of articles and played around with them a lot. I have to hand it to Sara Soueidan for taking the time to really explain SVG’s. You can find an overview of SVG sprite creation from her here https://24ways.org/2014/an-overview-of-svg-sprite-creation-techniques/. Sara also writes many great pieces on her website with respect to SVG and being a front-end Web developer https://sarasoueidan.com/articles/.

My end solution was less than ideal. I exported the icons individually from Figma; we are using Figma on this project as it is much easier for me to jump in and check styles as well as make changes as I am a designer turned developer. Then I would put it into Affinity Designer and export it… because it produced a smaller file size and seemed easier to play around with once in my editor. I then ran that file through SVGOMG which is a fantastic GUI SVG optimiser online. After that I put all of them into a single SVG element creating a symbol for each icon and sub icon used. I then used defs for all the reusable parts of the SVG’s and the unique parts of the icons. If this is super confusing I will show you step by step what I mean below.

This is how Figma exported it.

<svg width="430" height="430" viewBox="0 0 430 430" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>airport_circle</title><desc>Created using Figma</desc>
<g id="Canvas" transform="translate(3104 13293)">
<g id="airport_circled">
<g id=“ring">
<use xlink:href="#path0_stroke" transform="translate(-3101 -13290)" fill="#14ADBF"/>
</g>
<g id="airport">
<g id=“circle">
<use xlink:href="#path1_fill" transform="translate(-3087 -13277)" fill="#14ADBF"/>
</g>
<g id="plane">
<use xlink:href=“#plane" transform="translate(-3042 -13217.2)" fill="#FFFFFF"/>
</g>
</g>
</g>
</g>
<defs>
<path
id=“circle"
d="M 420.002 211.501C 420.002 326.653 326.653 420.002 211.501 420.002L 211.501 426.002C 329.967 426.002 426.002 329.967 426.002 211.501L 420.002 211.501ZM 211.501 420.002C 96.3491 420.002 3 326.653 3 211.501L -3 211.501C -3 329.967 93.0354 426.002 211.501 426.002L 211.501 420.002ZM 3 211.501C 3 96.3491 96.3491 3 211.501 3L 211.501 -3C 93.0354 -3 -3 93.0354 -3 211.501L 3 211.501ZM 211.501 3C 326.653 3 420.002 96.3491 420.002 211.501L 426.002 211.501C 426.002 93.0354 329.967 -3 211.501 -3L 211.501 3Z"/>
<path
id="path1_fill"
d="M 198 396C 307.352 396 396 307.352 396 198C 396 88.6476 307.352 0 198 0C 88.6476 0 0 88.6476 0 198C 0 307.352 88.6476 396 198 396Z"/>
<path
id=“plane"
d="M 107.666 123.185L 11.666 74.518C 11.666 74.518 5.446 72.349 9.333 66.518C 14 59.518 23 53.518 25 51.851C 27 50.184 33.666 46.185 38.333 46.518C 43 46.851 150.618 63.512 156.333 64.185C 162 64.852 163.821 64.365 167 60.852C 173.334 53.852 192.668 38.185 201.334 31.185C 219.495 16.515 262.322 -6.071 270.333 1.518C 276.666 7.518 273 31.185 243.667 63.851C 234.533 74.023 206.667 97.184 204.667 100.851C 202.667 104.518 201.667 106.518 202.667 110.851C 203.667 115.184 220.621 212.587 221.334 228.851C 221.465 231.84 222.667 239.851 217.667 243.184C 217.667 243.184 204.668 254.851 202.334 256.851C 200 258.851 196.334 261.852 192.334 255.518C 188.334 249.184 143.667 158.518 143.667 158.518C 143.667 158.518 95.333 200.852 94 201.518C 92.667 202.184 94.333 249.518 94.333 249.518L 74.333 266.519L 55.333 210.518L -3.66211e-07 189.518L 21 171.852L 64 174.185L 107.666 123.185Z"/>
</defs>
</svg>

This file was — 894 bytes

After SVGOMG — 620 bytes (30.65% saving)

Importing then exporting from Affinity Designer resulted in this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg
width="100%"
height="100%"
viewBox="0 0 430 430
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="airport_circled">
<path
id=“ring"
d="M423.002,214.501c0,115.152 -93.349,208.501 -208.501,208.501l0,6c118.466,0 214.501,-96.035 214.501,-214.501l-6,0Zm-208.501,208.501c-115.152,0 -208.501,-93.349 -208.501,-208.501l-6,0c0,118.466 96.035,214.501 214.501,214.501l0,-6Zm-208.501,-208.501c0,-115.152 93.349,-208.501 208.501,-208.501l0,-6c-118.466,0 -214.501,96.035 -214.501,214.501l6,0Zm208.501,-208.501c115.152,0 208.501,93.349 208.501,208.501l6,0c0,-118.466 -96.035,-214.501 -214.501,-214.501l0,6Z"
style="fill:#14adbf;fill-rule:nonzero;"/>
<g id="airport”>
<path id=“circle" d="M215,412c109.352,0 198,-88.648 198,-198c0,-109.352 -88.648,-198 -198,-198c-109.352,0 -198,88.648 -198,198c0,109.352 88.648,198 198,198Z" style="fill:#14adbf;fill-rule:nonzero;”/>
<path
id=“plane"
d="M169.666,198.985l-96,-48.667c0,0 -6.22,-2.169 -2.333,-8c4.667,-7 13.667,-13 15.667,-14.667c2,-1.667 8.666,-5.666 13.333,-5.333c4.667,0.333 112.285,16.994 118,17.667c5.667,0.667 7.488,0.18 10.667,-3.333c6.334,-7 25.668,-22.667 34.334,-29.667c18.161,-14.67 60.988,-37.256 68.999,-29.667c6.333,6 2.667,29.667 -26.666,62.333c-9.134,10.172 -37,33.333 -39,37c-2,3.667 -3,5.667 -2,10c1,4.333 17.954,101.736 18.667,118c0.131,2.989 1.333,11 -3.667,14.333c0,0 -12.999,11.667 -15.333,13.667c-2.334,2 -6,5.001 -10,-1.333c-4,-6.334 -48.667,-97 -48.667,-97c0,0 -48.334,42.334 -49.667,43c-1.333,0.666 0.333,48 0.333,48l-20,17.001l-19,-56.001l-55.333,-21l21,-17.666l43,2.333l43.666,-51Z"
style="fill:#fff;fill-rule:nonzero;"/></g>
</g>
</svg>

This file was — 906 bytes

After SVGOMG — 551 bytes (39.18% saving)

This may seem pedantic to be fussing over a few bytes but as I mentioned before it was more the ability to edit it and tweak it in the code that I was interested in.

I found this much easier to edit. Which I think is mostly due to the strange transform that Figma provides in it’s exports.

 transform=”translate(3104 13293)” 

I’ve never done any SVG animation stuff so perhaps the Figma one really is easier to work with.

Anyways onto my next step in this process.

NOTE: The \” is because I had to escape the string in React as I just placed it wholesale inside a string, I couldn’t be bothered going through and removing them all to for this. It is also important to close the <use> otherwise you will get the following one brought in. So you may have to do <use>….</use>

I wanted both the airport_circled and airport to be able to be referenced in two different places. For just this single icon and sub-icon I did this.

<svg  style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" fill-rule=\"evenodd\" clip-rule=\"evenodd\" stroke-linejoin=\"round\" stroke-miterlimit=\"1.4\">  <symbol id=\"airport_circled\" viewBox=\"0 0 430 430\">
<use xlink:href=\"#ring\"/>
<use xlink:href=\"#airport\"/>
</symbol>
<symbol id=\"airport\" viewBox=\"0 0 430 430\">
<use xlink:href=\"#circle\"/>
<use xlink:href=\"#airplane\"/>
</symbol>
<defs>
<path id=\"ring\" d=\"M423 214.5C423 329.7 329.7 423 214.5 423v6C333 429 429 333 429 214.5h-6zM214.5 423C99.3 423 6 329.7 6 214.5H0C0 333 96 429 214.5 429v-6zM6 214.5C6 99.3 99.3 6 214.5 6V0C96 0 0 96 0 214.5h6zM214.5 6C329.7 6 423 99.3 423 214.5h6C429 96 333 0 214.5 0v6z\" fill=\"#14adbf\"/>
<path id=\"circle\" fill=\"#14adbf\" d=\"M215 412c109.4 0 198-88.6 198-198S324.4 16 215 16 17 104.6 17 214s88.6 198 198 198z\"/> <path id=\"airplane\" fill=\"#fff\" d=\"M170 199.5l-96-48.8s-6.2-2.2-2.3-8c4.7-7 13.7-13 15.7-14.7 2-1.7 8.7-5.7 13.4-5.4 4.6.4 112.5 17 118.2 17.7 5.7.7 7.5.2 10.7-3.3 6.4-7 25.8-22.7 34.4-29.7 18-14.8 61-37.3 69-29.8 6.7 6 3 29.8-26.4 62.5-9.2 10.2-37 33.4-39 37-2 3.8-3 5.8-2 10 1 4.4 18 102 18.6 118.4 0 3 1.4 11-3.6 14.3 0 0-13 11.7-15.4 13.7-2.5 2-6 5-10-1.3-4-6-49-97-49-97s-48.4 42.6-49.7 43c-1.4 1 .3 48 .3 48l-20 17-19-56L62 266l21.2-17.8 43 2.4 44-51z\"/> </defs>
</svg>

Adding additional icons just required adding the new symbols and referencing the existing and new defs.

<svg ...>...OTHER SYMBOLS...  <symbol id=\"community_circled\" fill-rule=\"nonzero\" viewBox=\"0 0 430 430\">
<use xlink:href=\"#ring\"/>
<use xlink:href=\"#community\"/>
</symbol>
<symbol id=\"community\" viewBox=\"0 0 430 430\">
<use xlink:href=\"#circle\"/>
<use xlink:href=\"#people\"/>
</symbol>
<defs>...OTHER DEFS... <g id=\"people\" fill=\"#fff\">
<path id=\"path6_fill\" d=\"M236.3 174.4C248 167 256 154 256 139.2c0-22.7-18.5-41.2-41.3-41.2-22.7 0-41.2 18.5-41.2 41.3 0 15 8 28 20 35.3-7.4 3.8-40.6 27.7-24.6 149 0 0 20.5 4 42.4 4.4 20.6.5 38.2-1.5 49.8-4.5 0 0 21.5-124-25-149z\" />
<path id=\"path7_fill\" d=\"M149.3 204.5c9.7-6 16.2-16.7 16.2-29 0-18.8-15.2-34-34-34s-34 15.2-34 34c0 14 8.5 26 20.6 31.2-12.4 6-31.8 24.7-32.5 83.8 0 0 52.5 6 66 0 0 0-9-37.5 8-80 0 0-4-3.6-10.2-6z\" /> <path id=\"path8_fill\" d=\"M319 204.7c9-6 15-16.4 15-28.2 0-18.8-15.2-34-34-34s-34 15.2-34 34c0 10.5 4.7 20 12.2 26-2 1.6-4 4-7.7 7.5 0 0 11.5 13.5 8 79 0 0 52 4.5 65-1 0 0 10.5-45.5-23-81.5l-1.5-1.8z\" />
</g>
</defs>
</svg>

Referencing them inside your HTML is as simple as <use>.

<svg>
<use xlink:href=“airport_circled”></use>
</svg>

We don’t have to set a viewport because we already set them on the viewBox at the symbol level. No need for specifically positioning where the SVG is for spriting. The last thing I had left to fix was some SVG’s that I was using as background-images for repeating patterns. These were being sent down as separate files. Reducing the amount of requests is a huge deal with improving performance as latency is still the biggest issue with the speed of the internet. It’s much better to get a larger payload than to ask for every little thing that you need.

So I went for a look on the web again and had to navigate the territory of outdated methods and methods that have less support than others. Then I landed on this article. https://codepen.io/tigt/post/optimizing-svgs-in-data-uris. Which basically points out that you should URL encode instead of Base64 your SVG’s when you want to inline them in your CSS. There is even further ways to optimise the URL encoded SVG which is shown in JavaScript along with a link to a SASS implementation. I just settled for the unoptimised URL encoding as I had done enough and needed a break. If you want to inline your SVG into your CSS declarations to reduce the amount of requests you are making I highly suggest reading that article and here is a quick link to do your URL encoding http://meyerweb.com/eric/tools/dencoder/

And that summarises my learnings so far for doing web performance. I’m slowly getting the hang of devtools in the browsers and the various speed testing tools available.

I am looking forward to eventually learning about Service Workers and doing a lot more interesting things as I work towards building Progressive Web Apps.

--

--

Wade Dominic

Clojure programmer. Theta Healer. Organic food lover. Vegan.