#btconf Düsseldorf, Germany 08 - 09 Nov 2021

Harry Roberts

Harry is an independent Consultant Web Performance Engineer from the UK. He helps some of the world’s largest and most respected organisations find and fix their site-speed issues.

He is both a Google- and a Cloudinary Media Developer-Expert, and has consulted for clients from the United Nations to the BBC, General Electric to the Financial Times, and a whole host more.

When not doing work-work, he writes, teaches, and speaks about the entire gamut of front-end performance. When not doing work at all, he’s probably out riding his bike.

Want to watch this video on YouTube directly? This way, please.

Get Your <head> Straight

Despite being the only section of a website that a user never sees, the <head> is arguably the most important. It is bound to its own unique set of rules and often governs the overall speed of the page.

In this talk, we’ll look at some specific caveats, some fascinating intricacies, and — critically — the optimum order for a faster <head>. Find out why your <head> tags are so messy, so vital, and, I promise you, so interesting.



[Audience applause]

Harry Roberts: All right. So, this talk begins somewhat predictably at 3.5 thousand years ago with something called The Cardiocentric Theory.

The Cardiocentric Theory was the belief popularized by the ancient Egyptians that the heart was the single most important organ in the human body. Egyptians believed the heart was responsible for thought, emotion, intelligence, feeling. The Egyptians thought the heart was so important, it was the only organ they actually left inside the chest cavity after they’d embalmed the dead.

What would happen is the dead would sort of pass on to this not quite purgatory, but they’d go and meet up with Anubis, the God of Death, who would then weigh their heart against a feather. If the heart was lighter than a feather, they made it through to Egyptian afterlife.

I can’t help but think ancient Egyptian heaven was very, very quiet because, if you notice, that is not just a heart weighed against a feather. It’s a heart and a jaw. No one is getting into Egyptian heaven. All right. No one’s heart plus a jaw weighs less than a feather.

But this is the belief. They believed it was that important, it was the only real organ that was left inside the human body.

Now, fast forward a thousand years. It took a thousand years before someone else came up with the idea, the suggestion that maybe it’s the brain that’s most important. Maybe the brain controls feeling.

Hippocrates came up with the Cephalocentric Theory, ceph, the prefix (ceph) meaning of the head. But this never really caught on. It took a thousand years before someone first brought this up, and it never caught on. No one really thought the brain was that important.

Now, it was Hippocrates who first popularized the idea or, sorry, came up with the idea. It wasn’t until the 1500s that this guy, William Harvey, actually proved that, no, the brain is pretty important.

But ironically, he didn’t prove it by kind of proving the brain was important. He just worked out the heart does specifically this: The heart just pumps blood. Therefore, maybe the brain does something else.

But for about 2500, 3000 years, we had no idea just how important the head was. Human anatomy was completely misunderstood, and almost all the focus was put on the heart, everything from the neck down.

So, I think, in my work at least, we’ve got a similar kind of problem. I’m a Web performance engineer. I make websites faster for my clients. Nearly every developer I work with seems to subscribe to this Cardiocentric Theory. They completely misunderstand the importance of the head.

See what I did there. Very smart.

[Faint laughter]

Harry: Oh, wait! My clicker is going to give me a bit of trouble. I can tell.

In the next 40 minutes, you are going to learn way more than you ever thought possible about head tags, way more than you ever wanted to know. I am completely obsessed with head tags, and I can guarantee you (in about an hour), you will be as well.

So, what we’re going to do is we’re going to spend a bunch of time looking inside our head. Now, before we do, I want to cover a couple of key concepts - two things that are just going to--

To a lot of you, they’ll be really obvious, but they should make the rest of the talk just make a little more sense. Just things we need to remind ourselves of.

The first thing is that HTML is parsed line-by-line. That’s an immutable fact. HTML is parsed line-by-line. There is no control flow. There is no logic.

A parser has to step through HTML line-by-line. It cannot even see that line three exists until it’s finished parsing line two.

That’s why, for example, this wouldn’t work. If we were to try and console.log, foo isn’t defined yet. We can see here that (on line three) we’re trying to find out what foo is. The browser hasn’t even found foo. It’s on line six. That’s why this would throw an error. That’s why this doesn’t work. And if you’re a developer, you’d be very used to seeing things like this. This is a pointless example.

A slightly more meaningful example might be something like this. If you want to tell a visitor what the title of a page is, you need a title element. However, if you’ve got anything even remotely expensive in front of that -- and here it’s just another console.log -- the browser doesn’t even know there is a title element until it’s finished running that little bit of script. This is vital and this is a key concept present in every browser: HTML is parsed line-by-line.

The second important thing, and this actually kind of underpins the whole talk, really, your head is the single biggest render-blocking part of your document. And this sounds really simple just when I say it out loud, but if you stop and think the head is the single biggest render-blocking part of your document.

Because HTML is parsed line-by-line, the body tag, the opening body tag, cannot even be discovered until the closing head type is being finished. And the body is the only thing that actually is visible to a sighted user, right? So, until head is solved, the body might as well not even exist. What this basically means is you need to solve the head tag problem before you can even start to think about your body tags.

Now, to kind of prove this point -- not that I think you don’t believe me -- but to prove this point, here we’ve got a simple snippet, a style tag that’s just going to turn the background of the page red. And also, we’ve got a synchronous script in the head tag. Then we’ve got an H1. In the page, we’ve simply got a title.

Now, what’s going to happen is we load this page, and that synchronous script is going to block for X amount of time, which means we’re going to see nothing, nothing, nothing, and then red background, black text. So, prepare for this. It’s going to happen in the blink of an eye: nothing, nothing, nothing, everything.

Right? Everything appeared at the same time.

If we were to simply nudge that script out of the head and into the body, this is a diff, this is barely a change, right? But simply moving that script out of the head and into the body, we can discover the opening body tag far quicker, which means that now what happens is we get a slightly better-perceived performance.

This is an example for example’s sake, by the way. Don’t start moving all your scripts out of the head. I’m just proving a point. Before it was nothing, nothing, nothing, everything. Subtle change. Watch what happens now.

Simply nudging it out of the head means we can start rendering the body much sooner. We still were blocked for the title. The title still took a while to appear, but hopefully, this simple example helps kind of drive home the point that the head is the single biggest render-blocking part of your document, and that’s really what you want to solve if you’re going to solve anything to do with render tightening, whether that’s first paint, first contentful, or largest contentful.

So, for this talk, I created a baseline. I created the world’s slowest head tags, which is a bit redundant because all of my clients seem to have done this already. I created what I thought were going to be the world’s slowest head tags.

Now, if you’ve got your phones handy, you might want to take a picture of a couple of slides. This next slide, you might want to take a photograph of it just so you’ve got a reference while I’m speaking for the rest of the talk.

This is the head tag I kind of came up with. There is 41 lines of HTML there and most of them are empty space, right? There’s maybe 30, 35 lines of HTML, and we’ve got style sheets. We’ve got inline script. We’ve got a content security policy. We’ve got a cross-site request forgery token. This is all normal stuff that you would expect to see in head tags, right?

Sure, it’s a bit messy and the white space isn’t as nice as it could be, but nothing in here should be alarming. I would hope nothing in here was particularly alarming.

This is an absolute tram smash. This is such a mess.

I spent a lot of time. In case you haven’t noticed, I spent a lot of time looking at head tags. Immediately here, there’s a bunch of antipatterns, so I want you to take a look at this and see if you can spot them as well.

But this simple, innocuous-looking bit of HTML, once you run it through webpage tests and grab a waterfall, that’s what it looks like. Anyone who is used to reading waterfall charts - sorry. [Laughter] It’s a gross thing to look at.

But more or less, you wouldn’t expect something this disastrous. You wouldn’t expect--

For anyone who is not used to seeing waterfall charts, you probably wonder why I’m being so dramatic. But this is a real, real mess from those simple few lines of HTML. In fact, this is such a mess, it took us 9.3 seconds to start rendering the page. Forty-one lines of HTML took us 9.3 seconds.

What we’re going to do during this talk is refactor this. We’re not going to rebuild an application. We’re not going to switch from client-side rendering to server-side rendering. We’re doing nothing complicated. All we’re going to do in this talk is nudge some lines of HTML around and see if we can get this number any better.

So, the first bit of advice is, don’t be so big-headed. If it doesn’t need to be in there, get it out. If you can move it out of the head, do so. Not everything can be moved out of the head tags, but a lot of stuff can. Horrible, nasty sort of like your Facebook retargeting trackers, that kind of stuff, all that junk will work just as fine if you put it near the closing body tag.

You need to use your judgment here, but if you can get it out of the head, you absolutely should. So, if it does need to be there, get it out. Look for any inefficiencies in your head tags. I’m talking things like any redirects or any CSS that doesn’t need to be in the head.

You can put CSS files; you can put link rel=“stylesheet” in the body tags now. Every browser supports that, and it immediately unblocks rendering. I won’t go into too much detail here, but that’s a thing you can do nowadays.

Reduce the amount of stuff in the head in terms of don’t use all of bootstrap if you’re just needing its grid system. Reduce the side of payloads in the head. If you can get it out of there, you absolutely should.

One example in my demo of something we could remove from the head is this redirect. We’ve got a really annoying redirect here.

You’re probably thinking, “Harry, come on. I’m not an idiot. I’m not going to put a redirect in. I’m not going to have a redirect in my head tags. No one would do that.”

I see it surprisingly frequently, and I see it because of things normally like this. People linking to a third-party CDN for a certain package. In this case, jQuery. You just point @latest or @3, and this will resolve to an actual version number.

What we saw here is this redirect. That was 500 milliseconds. It’s half a second. In fact, just to give context for people who aren’t quite as boring as me, 500 milliseconds doesn’t seem like much, right? It doesn’t seem like a lot.

Speaking with Jon just before I got on stage, I worked with a client in the U.K. about four years ago and, together, we worked out that if we could make their website 300 milliseconds faster, just 300, they would make an extra 8 million pounds a year. So, if I can get rid of--

Oh, by the way, pro-tip: If I can find them 500 milliseconds just by removing a redirect, that’s great right? Never tell your clients what you do for them. I can’t submit an invoice which is, “Oh, I linked to the correct jQuery and you owe me five grand.” Never going to wash.

What you do is your invoice says, “I found you 8 million pounds a year, so I’ll take my slice.”

[Audience laughter]

Harry: Now, what is interesting here is that we can see on this graph, that stays 492 milliseconds. We got rid of this. It stands to reason we should have gained 492 milliseconds back.

Web performance is slightly non-deterministic and, when you run tests, you are subject to or you’re susceptible to variants in testing or variation in testing. So, across an average of 5 runs, even though this clearly says 492, realistically it only ended up being 41 milliseconds faster, which is odd. But that’s what our numbers said, and I have to kind of trust the numbers.

The next one is self-host whatever you can. Now, I’m starting real basic here. I’m starting with real basic advice, but a lot of my clients, they do miss this stuff. That means my first couple of hours working for them, I look like a hero, and it takes me a couple of minutes.

We’re going to get into the technical tricky stuff next, but this is another really basic one. Don’t use somebody else’s CDN - period. Don’t link to ajax.googleapis for your jQuery. Don’t use code.jquery. Don’t use even Google fonts. Don’t link out to anybody else for a third-party asset. Just don’t do it.

I’m not going to go into any detail because I don’t really have the time. But if you’re interested, I’ve written a full article detailing the dangers of using somebody else’s infrastructure. You can grab that and read that another time.

What I did now is I just self-hosted a couple of things. These files were all on a third-party domain. I’ve now ported them through the same domain. Now we can see we’re saving a bunch of time here simply self-hosting these files.

The only reason, by the way, that people don’t self-host their files, the only reason people do use code.jquery is because people believe it to be faster. Not using a third-party, 377 milliseconds quicker. It’s much faster to self-host your own files. What we’ve got now is a cumulative improvement of 418 milliseconds, which, like I said, to the right client, could be worth millions of euros a year.

This next one is quite interesting: Get your head checked out. I’m old enough that I remember XHTML 2.0, and I remember validating my XHTML fastidiously. I was so obsessed with validating my HTML.

No one has used the W3 validator for about eight years. I’m certain of it. Of course, you haven’t. Everyone is laughing like you haven’t even heard of it. That’s how much we don’t use it. However, I’m the one guy who still uses the validator because what’s really interesting is invalid head tags can cause real problems.

In the demo that I showed you, the snippet of code, we had this cross-site request forgery token. This is a pretty common way of doing it, and a hidden input. The problem is inputs aren’t allowed in your head tags. They’re an invalid element. In fact, there are only very few elements you are allowed to have the head.

Any element that isn’t on this list will throw an error in browsers, and browsers will correct in real-time. They’re very clever and very forgiving. But the problem is if the browser finds and input in the head tags, it panics and thinks, “You’re not allowed input in the head. I must be in the body,” and it will, on the fly, terminate the head early.

Actually, in the browser, the DOM is rendered like this. All of those other head tag elements--the HTTP-Equiv matter, the style block, this input--they’re all pushed into the body. This has ramifications when it comes to browser scheduling, prioritization, resource prioritization, and can cause genuine problems when it comes to pausing and requesting files.

Interestingly, just fixing this HTML error didn’t really yield too much of a change. Well, in fact, it kind of did. I need to get my head straight here because that was very contradictory.

What I’m trying to say is, I did not get the outcome I expected. A file went missing. We had one fewer request. I fixed these invalid head tags and we lost a request.

I was like, “That’s odd.” I didn’t delete something by accident. It genuinely went missing, and it made me realize something we’re going to look at later in the talk; it had a really interesting knock-on effect.

It was annoying because this is weird. What’s annoying is I got slower. [Laughter]

[Audience laughter]

Harry: What I’m not suggesting is you go and just break your head tags and get a faster website. Even as a performance engineer, I would always prefer to have predictable, correct code that’s a little bit slower than code that could be nondeterministic, it depends how the browser handles it, et cetera.

Actually, we got 140 milliseconds slower for a cumulative improvement of just 280 at this point.

Okay, definitely get your phones ready now. This entire talk hinges around this next slide. I’ve done a lot of research for the last three years working out the absolute perfect order for your head tags, and it ended up just being one slide. Out of 107 slides, the most important one is just one that I’m going to cover really quickly, so get ready to take a picture. This is the optimum head tag order.

That little asterisk there is to remind me to tell you that this is subject to change because things may change in browsers in the future. This is the current best sort of order based on all of my extensive testing. But there will be caveats and implementation details where, on your site, you might not be able to just simply replicate this.

Here is the optimum head tag order. For the rest of this talk, we are only going to discuss the things in bright white. Everything that’s dimmed out is not a scope of discussion for this talk. That’s because everything that is grayed out is asynchronous. It will not block rendering anyway, so it’s kind of moot. It’s kind of irrelevant where you put these things (in gray), for the purposes of this talk at least.

Everything that’s in bright white is going to be something that’s really important to rendering the page. This is the optimum head tag order.

Now, I’m around the next could of days. If anyone wants to know anything about the things that are grayed out, come and find me. We’ll grab a coffee, and I can tell you all about that. But like I say, for the purposes of this talk, all we really care about are the things that are render-blocking.

The first thing that I found is really interesting. It’s not quite a bug, but the first thing I found when I was researching this is if you’ve got a content security policy -- Meta CSP -- if you’ve got a CSP defined as a metatag, there’s a chance you’re going to run into huge problems.

Anyone who has implemented CSP before (content security policy), it’s a complete pain in the ass. It’s a real nightmare. It’s a horrible thing to implement. But normally if you implement CSP (content security policies), you would do it with an HTTP response header. That’s the most normal way of doing it.

For those who haven’t heard of CSP, don’t bother looking it up. It’s dreadful, but it basically tells a browser what kind of files it is allowed to download from which specific domains. It’s a security thing.

You can implement CSP in a metatag in the head. But if you do, it’s going to disable the preload scanner. I can’t really see that well but put your hands up if you have heard of the preload scanner.

There are my five friends.

[Audience laughter]

Harry: For those of you who haven’t, this is a feature present in every single browser. Basically, in the olden days (IE7), browsers were really slow at downloading things because they only had one parser. There was one engine that was responsible for parsing the HTML, discovering files, downloading files, executing the files, and then moving on, sort of rinse and repeat over and over.

What it means is lots of starting and stopping because if a browser builds a bit of HTML and then discovers that it’s a script tag and there’s a script elsewhere, it has to stop, download the script, run it, and then proceed really, really, really slow. So, in IE8--

Thanks, Microsoft. Who would have thought? Oh, actually, because it was Microsoft, they actually called it the Speculative Pre-Parser. I guess that isn’t a surprise. Everyone else calls it preload scanner.

Preload scanner is a secondary inert parser that’s allowed to just run ahead and just do downloads. It’s asynchronous. That is what makes browsers as fast as they are. In fact, it made the Web (in general) around 20% faster just this one invention inside a browser.

Developers get it for free. The reason you’ve never heard of it is because it’s just always there. It’s just always working.

If you put a meta CSP in the middle of your head tags, Chrome will disable the preload scanner. Anyway, any browser that doesn’t have a preload scanner, basically this is what IE7 used to look like. That would be the process of downloading four JavaScript files. It has to happen one after the other, and you can see how this is slow. Everything is dragged out very serialized.

Simply by inventing a preload scanner, IE8 moved us to a model a little more like this. Everything done in parallel. You can see how much faster that is. Way nicer.

I was working for a client, and this client was really kind of getting stressed out. They were like, “Harry, we’ve got real big problems with first contentful paint. Start render is really, really bad. It’s really slow. But we can’t work out why. We’ve increased our spend with Cloudflare. We’ve got the most expensive hosting we can get.”

They were spending tens of thousands of dollars on this problem, and I was like, “Well, if it’s a start render problem, it’s almost always going to be in the head,” because the head is the biggest render-blocking part of the page. So, I ran a webpage test. You can all see that, right?

[Audience whistles and laughs]

Harry: I’ve never seen a waterfall chart start with “Once upon a time,” before. [Laughter]

[Audience laughs]

Harry: But this is a fucking tale. Let’s zoom in. I isolated this client’s head tags, and it’s a real mess. You can see that, generally, here are the first sort of ten files are downloading in parallel. Then we get this horribly dragged out sort of middle part of the chart. Then towards the end here, sort of the line 30 onwards, we start getting loads of stuff in parallel again.

Well, there’s obviously something wrong in the head. Something is blocking rendering.

Now, these little colorful like green, orange, and purple bits, this tells us that we are going to a different domain. A third party is where this file lives. I thought, “I need to rule out is this a first-party problem or a third-party problem?”

I reran the test blocking all third parties and the problem became even more apparent. You can see there’s this real stepped part in the middle here sort of line ten onwards. Clearly, step, step, step.

This is very unusual and, like I say, I’m old enough that I’m like, “I’ve seen this before. I’ve seen this in IE7.” That’s a flashback that nobody wants.

I was like, “There’s clearly something going on that’s blocking the preload scanner, breaking the preload scanner.” It was a case of a manual drop. I just had to go and look through the head tags line-by-line and look for anything unusual.

The only thing that stuck out to me was this: an HTTP header equivalent metatag that defined the content security policy. Remember, CSP tells the browser what files it is allowed to download and what it isn’t from certain different domains.

In this case, it was just upgrading secure requests, but it turns out this being in the middle of the head tags disables the preload scanner. And it does it on purpose because here is the source code for Chromium’s preload scanner, and it says here, “Don’t preload anything if a CSP metatag is found.”

It makes complete sense when you think about it. If the browser finds a policy that says, “Oh, be careful what you download from where,” downloading will take longer because, basically, if you’ve got a load of files and then your security policy, the browser is going to panic. That’s the problem.

Also, what it says here is, “We should rarely find them here because,” blah-blah-blah-blah-blah. “We should rarely find them.”

So, I spoke to a friend who is much cleverer than I am. Most of my friends are much cleverer than I am. I asked him to query the HTTP archive and find out just how many Web pages (not websites but individual Web pages) this affects. It was actually a really small number: 8500 -- that was it -- webpages, not even websites. This is rare, so the chance it affects anyone in this room is very, very, very slim.

But I moved this line of HTML to the top of the head tags rather than the middle of the head tags, and we saw a tremendous change. In our demo case, we saved this much time.

The other interesting thing is, when the preload scanner does get disabled, so if things download one after the other, but if the preload scanner is disabled for this meta CSP, you also get double downloads. This is also part of the reason why fixing the head tags made one request disappear.

There were too many requests in this waterfall to start with. And the reason we have double downloads is if the browser has started downloading a bunch of stuff and then finds a content security policy, it’s going to redownload those files again with the new information because, remember HTML is parsed line-by-line.

If you’ve told the browser, “Hey, go and download these files,” and then the browser finds out, “Ah, shit. I should have probably requested those a bit differently,” it’s going to go back and start all over again.

The two-fold problem here is that you lose the preload scanner, which means it’s slow. Also, you will incur double-downloads because the browser has to re-request those potentially insecure files.

Just moving this one line of HTML to the top of the head tags saved us 3.7 seconds - just moving one line of HTML. Again, never tell your clients what you did. Tell them the outcomes of what you did.

We’re now about four seconds faster basically for free. We’ve just nudged a few lines of HTML around. This is specifically about the CSP metatag.

The next thing I want to talk about is metadata in general. Basically, the next thing in your head should be general metadata about the page. When I’m talking about meta here, these two asterisks are to remind me to tell you that I’m not talking about your Twitter, Open Graph stuff, or your manifest file. All those metatags are for your SEO team and your marketing team. Those don’t count here.

What I’m talking about is metadata about the page. How should the browser treat that page?

Tell a browser how to deal with the page immediately. Make sure the browser understands what character encoding it should be using. Should it be rendering a zoomable page or not? What is the meta viewport?

In fact, the spec even tells us. The spec outlines quite clearly your character encoding should appear in the first kilobyte of the document. That’s not the first compressed kilobyte. That’s the first kilobyte decompressed.

The spec says it needs to appear that soon because, if not, remember, HTML is parsed line-by-line. The browser is going to start parsing the page in the browser’s character encoding. And that’s usually UTF-8. But, if halfway down the page there’s a character encoding that says, “No, this is ANSI (for some reason),” the browser is going to have to re-parse the entire page as a brand new character encoding. So, that’s going to basically double up the parsing workload.

Here's a pointless little demo I made. Here’s what happens if you put your meta viewport at the bottom of the page. The page renders as a zoomed-out desktop kind of view and then, because HTML is parsed line-by-line, then it finds your meta viewport. It has to re-render the entire page, so it doesn’t have to re-parse it, but it will have to repaint it.

I’ll play that again. I don’t know why. It’s not a particularly interesting video. I don’t know why I’m playing it again, but I’ve committed to it now.

If you put your meta viewport tag in the wrong place, you’re going to render the page as a zoomed-out desktop version. Then the browser finds it and then has to repaint the whole thing responsibly.

Basically, yeah, anything to do with how the browser deals with the page, get that very, very early in the head.

Don’t hide the title. This is an interesting one that has affected a couple of my clients. Usually with Optimizely or any A/B testing tools that kind of block the parse of a client a long time, they end up hiding their title of the page from their visitors. Oftentimes, the title, as a visitor, is the very first impression of that webpage or that website.

What I’m talking about here is HTML is parsed line-by-line. I can’t remember if I’ve mentioned that yet. HTML is parsed line-by-line, so if line three is going to take a second, we don’t even know if there is a line four. Therefore, we can’t put a title in the browser tab, right?

For example, what could happen is, watch this. Look at the tab. That just says the current URL. This site looks down to me.

All right. We’ll do that again.

It literally just says the URL of the page. No actual feedback there. This looks broken.

If I take this snippet and just swap those two around, look at the browser’s title tab again. At least we can immediately tell the user this page has responded. You’ve got a title. We can’t render it yet, but the page is working and we’re in the right place.

Don’t put anything parser blocking. Don’t put anything blocking at all ahead of your title because you risk this happening, obscuring that information.

Basically, what you don’t want to do is have the title of the page the same as your start render. There’s no reason those two should be the same number. You should show the title as soon as you can.

Now, moving metadata around didn’t change the shape of the waterfall at all because we’re not dealing with files at this point. We haven’t moved any files. So, the waterfall, the before and after, are visually identical.

However -- and this really, really surprised me -- that change was 280 milliseconds faster, which really surprised me because all I did is move some metatags around.

When I did these tests, by the way, I ran five tests every time and then sort of averaged it. None of the tests were anomalous. There wasn’t any erroneous data. I did my due diligence, and I did this properly.

Once I took the median of 5 tests, it was 280 milliseconds faster. This really surprised me (just moving metadata around).

Okay. Now, we’re about to get onto the good stuff. This is all about how your JavaScript and CSS interact with each other and how important it is that we get that correct.

There are some really fascinating things about browser internals that sincerely I’ve met very few developers who actually knew this stuff. It doesn’t really get talked. It’s not very obvious. You can’t really see it happening. But once you’re aware of it, it’s going to really keep you up at night.

Synchronous JavaScript needs to go before CSS. People hate this. People think, “Well, CSS is render-blocking, so I should put that first. Right? That should be the first thing in front of the browser.” Absolutely not the case.

In the head tag order, your synchronous JavaScript goes before your synchronous CSS. That’s because CSS blocks execution of JavaScript. Any CSS will block the execution of any subsequent JavaScript. Most people I’ve met have no idea this was true, but your browser will not run any JavaScript if it’s currently downloading any CSS.

There’s a really simple reason for this, and it’s a really annoying reason. It’s a defensive strategy. Basically, the browser needs to defend itself one percent of the time, but this happens 100% of the time. It’s to get around the following problem.

Here we’ve got a stylesheet, style.css. Then immediately after it, we get compute style. We get the color of the page.

A browser doesn’t know what a script is going to do until it’s doing it, so the browser doesn’t know if this script tag is going to ask a CSS question or not. So, if the browser did this asynchronously, that script will probably run before the CSS is downloaded. You’re going to get a stale or incorrect answer. It’ll just tell you the page is black because there are no styles applied to it yet.

The browser doesn’t know what this script is going to do, and that script could ask a CSS question. So, defensively, the browser will not run that script until it’s got all the CSS and worked out the CSS object model just in case the script asks a CSS question. It’s very unlikely your script will need to do this, but the browser will defensively carry this out every single time. A browser will not run any JavaScript if it’s currently working on CSS. This is an example for example’s sake.

Where it does become a little more interesting--

I’m getting double-clicks, and it’s really annoying me.

Examples such as this. We’ve got here line 7 through 11. We’ve got a little async snippet. Loads of third parties give you snippets like this to implement analytics or whatever it might be. You’ve seen these loads of times.

Now, that script isn’t going to run until the CSS is finished. If that CSS takes half a second to download, that means the browser has got to wait half a second before it will even run this script.

The way that manifests itself is analytics.js doesn’t even get discovered, so line three doesn’t even get discovered until the moment line two is finished. This is complete serialization. We’ve lost the chance to parallelize anything here. That script wasn’t allowed to run until that CSS is finished.

What you do here is you just swap them around.

Now I’m getting zero clicks.

What you do is just swap these around.

Very poignant that (eight years later in the same room) this thing finally dies.

Swap these two around, right? Put your script first and we get complete parallelization. Those now happen at the exact same time - way faster.

Here’s the thing that a lot of people don’t know as well. These snippets, this snippet that’s now sort of line three, any JavaScript that is injected by definition will be asynchronous. That’s what the spec says. This is why we use these snippets. Injected JavaScript files are non-blocking.

What’s interesting here is this hasn’t changed first contentful paint. We haven’t unblocked rendering here because, regardless, this JavaScript wasn’t blocking rendering. But what we have done here is we can run the JavaScript earlier. That means if it isn’t analytics package, we can capture the data sooner.

If it provides functionality to the page, that functionality is available sooner. If it’s an A/B testing tool, we can run the test hopefully before the user sees it happen.

This hasn’t improved start render, but what it has done is it’s given us access to the JavaScript’s functionality in parallel with CSS.

Now, when install Google Analytics or Google Tag Manager, it actually says here, “Place the script at the very top of the head.” Most developers, because we’re good people (for the most part), most developers think, “Piss off. I’m not putting you at the top of my head. I’m putting you at the bottom.”

Tag Manager isn’t telling you to do this for nefarious, like, “Oh, we can capture more data that way.” It’s just that it’s genuinely faster. Place synchronous JavaScript ahead of synchronous CSS, except--

This is where it gets weird. It turns out, it depends. It’s a bug or a feature depending on how lazy you’re feeling. I think it’s a bug. Chrome developers think it’s a feature, which means they don’t need to fix it.

Synchronous JavaScript is blocked by CSS that contains imports. This is so weird. The little dagger is to remind me to tell you, don’t use @import anyway. It’s a terrible idea. Don’t use import in CSS. Just don’t.

But if you’ve got a third-party such as I think MyFonts, maybe Typekit, they force you to use @import, which is unfortunate. If you’ve got a third party that forces you to use imports, what you need to do is make sure your synchronous JavaScript goes after that import.

Basically, here we’ve got a synchronous JavaScript file, and this CSS file defined after it simple @imports another one. Running this through a webpage test gives us a really, really interesting phenomenon. Line two was responsible for discovering and downloading line four. Line two is a CSS file that simply contains an @import. But look at the gap. Look at the gap here. That’s incredible.

For some reason, that second style sheet wasn’t even discovered until the moment the JavaScript had finished. The reason this is fascinating, browsers are single-threaded. They can only do one thing at once. So, what this means is it downloads the style sheet, then immediately starts downloading a JavaScript file. It’s like, “Well, I’m in the scripting phase.” That sort of frame lifecycle is script and then style with bits in between.

What happens is the browser downloads a CSS file and then immediately has a JavaScript file. It’s like, “Well, this is a synchronous file. I’ve got to run it.” In order to run that JavaScript file, it’s got to wait until it downloads.

Here’s the thing. In order to discover the import, the browser also has to do a re-calc style, and that’s the style phase of the frame. This is way too much to just verbalize. I should probably have a slide that explains this.

Basically, the browser needs to do a re-calc style on that first style sheet to even discover the @import. But it can’t do a re-calc style until it’s finished its scripting task. This is a really strange phenomenon where the browser can’t discover the import until it’s finished working on this JavaScript. They’re completely unrelated, and the Chrome team had no idea this existed.

I did raise a bug and, like I say, they’re of the opinion that it’s too much work to fix. It’s a feature.

Basically, you take this--

We’re back.

We take this, and we swap them around, and the waterfall chart now looks a lot more like this. Way faster. Way, way faster.

Avoid import anyway. Just don’t do it. But if you have to, make sure your CSS goes before the JavaScript.

That’s actually helped me prove a point because I don’t expect you to memorize this. I’ve got a cheat sheet for you later.

Here is the Vans Canada website. What’s really interesting is their start render isn’t until 15 seconds. That’s terrifying - 15 seconds.

The Vans website is a bit of a car crash, unfortunately, and their head tag has got over a hundred, like 144 files in their head tags.

Do you know? This isn’t really their fault? It’s because Next.js is really overzealous with preloading, so they made loads of tiny little bundles. Then Next.js goes and just preloads them all, so the head tags for the Vans Canada website are enormous.

But see this JavaScript file on line 142? That needs to run. Then it downloads the next one. And that CSS file is blocked behind that really long JavaScript. That’s because that CSS file is @imported.

Basically, this last render-blocking file, this CSS file on line 144, that last render-blocking file is for some reason trapped for nearly 13 seconds behind this slow loading JavaScript. If they move that CSS before the JavaScript, it completely solved the problem. That’s all they need to do.

I should probably email them about this rather than tell you lot. I didn’t work for Vans. I just noticed that someone said the site felt slow. I was like, “Oh, that’s why.” I should probably let them know.

I did raise a bug. [Balk] It’s probably not going to get fixed.

Just reordering synchronous CSS after synchronous JavaScript and imports before JavaScript, reordering this stuff, just moving lines around, the waterfall now looked like this. That huge bit of inline script is now defined before the CSS files.

If you look on line one at the top, that script doesn’t run until about four seconds. Now it runs as soon as the HTML has arrived. It’s about 3.5 seconds faster to run that inline script because it isn’t being blocked by any of the CSS anymore. Just reordering these files, we find that we saved 1.6 seconds just by reordering CSS and JavaScript.

Then -- because I keep saying it, don’t use imports, just don’t use it -- I just got rid of the import and used a second link rel=“style sheet” in the head. Basically, I added one more link rel=“style sheet” tag and removed the import from the CSS file.

We then got to this where now we don’t have to wait for the CSS to be parsed before we can discover that last file. Just doing that, just removing an import saved us 750 milliseconds, three-quarters of a second just by removing an import.

What I find fascinating here is we haven’t rebuilt the application. We haven’t fundamentally changed how the site works. We haven’t re-platformed. We’ve just nudged some files around and, at this point, we’re 6.6 seconds faster just by nudging some lines around.

For anyone who does work in SEO or the marketing department, or for anyone here worried about what the marketing department is going to tell you, I am going to address that issue. Just put your SEO and social stuff, put that at the end of the head. Just put it at the end. It’s going to work. It’s going to be fine because here’s how I explain it to SEO people.

SEO people say, “No, no! If we put it too low down, Googlebot might not find it. It might not find that data.”

Remember, HTML is parsed--

Audience: Line-by-line.

Harry: Line-by-line. HTML is parsed line-by-line. What this means is if Googlebot can’t find the bottom of your head tags, it can’t find the start of your body tags, which means your pages aren’t getting indexed anyway.

If you think about it logically, if your SEO manager is worried, “Oh, what if Googlebot doesn’t make it to the bottom of the head?” they should be double terrified that it, therefore, hasn’t made it to the start of the body.

[Audience laughs]

Harry: And you’re proper fucked.

Basically, if Googlebot can’t find your metatags, it can’t find your content. There is one really interesting sort of almost exception.

I tweeted about this, and my friend -- again, it’s the same friend from earlier, the friend that’s cleverer than me, a guy called Barry -- he did some quick tests.

You know when you tweet something and you get the card, the open graph card that shows the title and a brief description of the URL. Or in iMessage, you drop a message to a friend and it’ll show you a snippet of the webpage you’ve sent them? Most services do a full get request. It’ll download the entire HTML of that link you’ve sent your friend, and it will just look through the whole HTML for the relevant metatags.

The only exceptions are WhatsApp, which gets the first megabyte of the HTML, and it will look in the first megabyte of HTML for those metatags. This is compressed HTML as well. So, if you’ve got a compressed page more than a megabyte, then the last thing you need to worry about is WhatsApp.

[Audience laughs]

Harry: Slack does a range request also, but it just gets the first 32 kilobytes of compressed HTML. But again, if your head tags are bigger than 32 compressed kilobytes, don’t be worried about what people see in Slack. Be worried about how people experience your website for real.

The only exceptions are Slack and WhatsApp, but they’re not really. They’re not relevant because there is no way your head tags will be bigger than 32 compressed kilobytes. It’s just not going to happen - until it does.

Slack actually says this: “It fetches as little of the page as it can (using a range request).” Slack, this is a very deliberate thing from them.

Oh, I’ve only got three minutes left and quite a lot still to cover.

Basically, a new order. I rewrote this HTML. We rewrote this HTML this morning. Now it looks like -- well, now first contentful paint is 2.7 seconds. It was 9.3. It’s now 2.6. It could get a lot faster as well. But what I actually did for my demos is I made sure that every single file took one second to download just to exaggerate the waterfalls for you. So, it’s not actually 2.6 seconds. Realistically, it would be much faster.

Yeah, a fair test. I made sure every file took at least a second to download.

The new order is this. Synchronous. Well, metadata first, then the title, then all of my synchronous JavaScript, then all of my synchronous CSS, and that’s it. That’s the new order. There were no social metatags in this example, but they’d be very last.

This is an ugly slide. I should have deleted this. That’s just a diff. If you wanted to actually look at the slides online, you can actually see what I changed.

The key thing is, let’s look at the before and after of the waterfalls. The waterfalls on the same timeframe, the same X-axis are very, very different beasts now. This is just from moving a few lines of HTML around.

Now, this is a really boring slide, and it’s going to take about nine seconds. I’m bored of this. I’m so sorry I’m doing this to you. That was very boring.

You see we’re fully rendered. We’re finished at 2.8 seconds. The other one in 9.4. A vast difference.

All right. I started this talk with a bit of - I don’t know - pretentious talk about medical history, which you can tell I hadn’t researched that well. I want to go back to a bit of medical science now.

Computer tomography or CT scans. Whenever I’m doing this work for clients, it’s just a lot of stuff for me to memory. There’s a lot of stuff I have to just remember and look out for. I started thinking a few minutes ago, like, it’d be great to have a little tool that could just do this for me. I’d like to just have a quick look inside someone’s head.

Well, the way we do that for people is with a CT scan, right? I was thinking, well, why don’t I try and build a CT scanner for Web pages?

Because I’m a bit of a fool, I thought, well, I’m going to build it in CSS. Right? What’s the most inappropriate language syntax for this job? It was CSS, so that’s what I did.

I created a little snippet called ct.css that you can just install into your app. It’s actually a browser bookmark. Anyone use those anymore?

You can just go to any webpage, click this thing, and it will show you this. This is the test page, and it will show you, “Hey, look. This inline script is completely fine. it’s an inline script. There’s no network request, and it’s nice and early. Async attribute is redundant, blah-blah-blah.”

It will show you loads of antipatterns and stuff that might be incorrect with your head tags. The color of the borders highlight severity, so red is like, “This is a problem.”

If the border is solid, it means this file is the file we’re talking about. If the border is dotted, it’s saying that another file is causing this file problems. You can basically work out, is this file being blocked, or is this file blocking?

This was, well, like calling someone your ex’s name by accident because this is actually for a different conference website. I’m sorry. [Laughter] I did a workshop at a conference a couple of weeks ago, and this is what their head tags looked like.

CNN, the CNN website broke the tool, so that’s very on-brand. Everyone dumps all over CNN. It’s horrible. Everyone talks about how bad CNN is, but CNN clearly don’t send staff to conferences because, if they did, CNN would have the fastest website. It’s in every demo. If they just went to conferences, people are fixing CNN for free.

If you want to use this, it’s as simple as that. Just include the stylesheet. You can hotlink it.

Obviously, that’s going to get messy in your development environment. You can create a browser bookmark basically, if you just save this as a bookmark in your browser.

[Audience member sneezes]

Harry: Bless you.

You can just create a little bookmark, and it will just work on the fly for any page you visit.

If you want to see it and its source code, it’s right there. It’s fairly useful. It’s not foolproof. It’s not bulletproof. But it’s a really great starting point for just quickly trying to work out where your liabilities are.

You don’t have to memorize all this stuff. What a waste of your time. I just spent 45 minutes telling you, “Don’t remember anything I said. Just use this.”

[Audience laughs]

Harry: But you have been very patient and it has been 45 minutes. I just want to say thank you very much for your time.

[Audience applause]