If you want to improve page performance on your website, you need to understand the load order of the page. Here we’ll be talking in particular about javascript load order and how it affects the load of a web page.

Background

When we talk about web performance in terms of users, we talk about page experience. Page experience, in this context, does not focus on the quality of content on the page, but rather the smoothness and timeliness with which that content is delivered.

In order to give users a great page experience on your website, the goal is as simple as getting a fully loaded and settled page in front of the user as quickly as possible. That is to say we want:

  • All text content rendered
  • All images rendered
  • Stable DOM layout. e.g. No shuffling of content as new images or adverts are added
  • Interaction ready. e.g. button handlers ready before the user tries to click

This process goes into an incredible of detail and nuance. Here we will only focus on the various options for inserting javascript into the page, how they behave, and their consequences.

A Simple Page

Normally a web page would be a lot more complex, but the principles stay the same, so let’s keep it clear with simplicity. Let’s start with a super simple HTML page:

<html>
  <head>
    <title>Javascript load order</title>
  </head>

  <body>
    <div id="main">
      I am content directly from the HTML
    </div>
  </body>
</html>

Running that (using http-server) in the browser we’ll see:

Simple Page

Without going into the incredible detail that made that happen, we can see a simple page has been rendered. Now, assuming like most modern web pages we’ll want to add some javascript. There are 2 options:

  1. Inline script
  2. External script

Here we’ll keep it simple and deal with inline scripts. A further explanation for external scripts will come in a follow up.

Inline script

Inline scripts are snippets of javascript that are embedded directly into the HTML.

<script>
  console.log('hello, world');
</script>
They can be added anywhere in the <head> tag or anywhere in the <body> tag. The fact they are embedded directly means:

  1. There is no extra download to fetch the script
  2. The initial HTML payload increases in size

These 2 forces need to be balanced against each other to achieve the best page load experience. While that discussion is out of scope in this article, it is worth noting that we generally talk in terms of “critical” (i.e. whatever is deemed to be the minimum javascript required for the initial page load) javascript being put inline, and non-critical javascript being put in external files .

Let’s add some javascript to the <head> tag. Our head tag now looks like this:

<head>
  <title>Javascript load order</title>
  <script>
    console.log('hello from an inline script');
  </script>
</head>

Inline script in head tag

That’s great! Our script has logged some output to the console as expected. However we haven’t figured out much about load order here. In order to demonstrate the render blocking nature of the script, let’s tweak our code to update the background colour of the <div>. The code now looks like this:

<html>

<head>
  <title>Javascript load order</title>
  <script>
    const elem = document.getElementById('main');
    elem.style.background = 'blue';
    elem.style.color = 'white';
  </script>
</head>

<body>
  <div id="main">
    I am content directly from the HTML
  </div>
</body>

</html>

As you can see we’re doing some fairly basic DOM manipulation here:

  • find the element with id “main”
  • change it’s background and foreground colours
  • say humblest of apologies to the css overlords for that oversimplified code

However, here’s the output: Inline script in head tag with console error showing null reference exception

Oh no! That didn’t work at all! The colours have not changed, and there is an error in the console. What’s going on here?

Render Blocking

The whole reason we care about javascript load order and execution is because javascript is one of what are known as “render blocking resources” on a web page. A render blocking resource is essentially just that - a resource that blocks further rendering on the page until it has been fully processed.

To understand the concept of render blocking resources all you have to do is understand that the browser is going to process the HTML as you would read a page of a book.*

*note that there are some subtleties in there (of course there are) but they are not important for this discussion.

Trying to manipulate an element that does not exist

In our example above, this means that the javascript in the <head> tag is executed BEFORE the <div id="main"> is rendered (technically, added to the DOM). That means for the code that is trying to get the element by id:

const elem = document.getElementById('main');
there is no such existing element in the DOM yet (because that happens further down in the HTML), and elem is therefore null. Given that elem is null, the next line will obviously fail with the “cannot read properties of null” exception.

elem.style.background = 'blue';

To recap the important bits in bullet points:

  • browser begins to process the html (reading top to bottom like a book)
  • browser executes javascript in the <head>, trying to getElementById("main")
  • no such element exists so we have the elem variable with a null value
  • exception is thrown when trying to read elem.style.background because elem is null
  • browser logs the exception to the console
  • browser continues to process the rest of the html AFTER the <script> tag
  • browser adds <div id="main"> to the DOM

The <script> block is executed in isolation i.e kind of as if it had a massive try/catch block around it. That is why we saw the javascript exception, but the rest of the page was still processed and rendered.

Javascript can manipulate elements that already exist

We know from the discussion so far that our script threw an exception because it was trying to manipulate a DOM element that did not yet exist. From that we can figure out there is a simple solution - move the script tag BELOW (remember the book analogy) the DOM elements it is trying to manipulate.

<html>

<head>
  <title>Javascript load order</title>
</head>

<body>
  <div id="main">
    I am content directly from the HTML
  </div>

  <script>
    const elem = document.getElementById('main');
    elem.style.background = 'blue';
    elem.style.color = 'white';
  </script>
</body>

</html>

And yay! There is no console error and our script has indeed changed the colours of the <div> Inline script at bottom of html with div now blue with white text

In summary:

  • the browser process HTML top to bottom
  • when a <script> tag is encountered, that javascript is executed and all further rendering is blocked until after that execution is complete
  • javascript cannot manipulate elements that do not exist in the DOM

Have a play with the code at https://github.com/djarcher/tutorial-inline-script-execution

Thank you for reading.

This is part of a series, gently introducing web performance concepts.

Tags: #frontend