How to optimize browser tasks related to rendering the DOM
Find the right balance of tasks to defer or prioritize for better DOM rendering speed.
Optimization requires us to find the right balance of tasks that pause the DOM and tasks that can be deferred until after it loads.
Placement of CSS and JS references
As we've discussed, the HTML parser evaluates nodes from the top down and from parent to child.
<html>
<head>
<title>This title appears in the browser's tab</title>
<script src="main.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Heading that gets rendered</h1>
</body>
</html>
The <head></head>
tags contain nodes that are not rendered (title, meta, scripts, links, etc.), while nodes within the <body>
are visually painted (h1, p, div, img, etc.) for the user to view and interact with.
When the DOM parser encounters a standard <script src>
or <link rel="stylesheet" href>
tag, the resource is downloaded and parsed before DOM construction continues. Inline scripts and styles pause construction while they are parsed.
Head Vs Body script location
It's a common practice to reference all scripts and styles within the head tags of the HTML. However, this is frequently not necessary and can significantly delay the HTML parser.
When only loading scripts and styles in the <head>
, the page renders content all at once.
<html>
<head>
<script src="/dist/js/debug/delay.js"></script>
<script>
delay(3000)
</script>
</head>
<body>
...
The <head>
node will pause DOM construction by at least 3 seconds, delaying discovery of the <body>
.
The screen changes from blank to fully rendered.
Let's move our delay JS to just before the closing </body>
tag.
...
<script src="/dist/js/debug/delay.js"></script>
<script>
delay(3000)
</script>
</body>
Notice that instead of a blank screen, we quickly see some elements on our page. Placing our script towards the end of the body means that we probably aren't delaying the DOMParser from finding and rendering any important nodes.
By differing when the DOM parser encounters scripts or styles, we can benefit from incremental rendering.
Incremental Building and Rendering
Let's demonstrate this incremental building and rendering process by updating our <main>
section to include the delay script between <h2>
elements.
<body>
<main>
<h2>Before Delay 3000</h2>
<script src="/dist/js/debug/delay.js"></script>
<script>
delay(3000)
</script>
<h2>After Delay 3000</h2>
<h2>Before Delay 3200</h2>
<script>
delay(3200)
</script>
<h2>After Delay 3200</h2>
<h2>Before Delay 3100</h2>
<script>
delay(3100)
</script>
<h2>After Delay 3100</h2>
<h2>End Delay</h2>
</main>
</body>
And if we examine our page, these nodes have a noticeable delay before being rendered. For non-critical or progressively enhanced content, we can utilize incremental building to reduce DOM parser delays of critical, above-the-fold content.
Prioritizing Above the Fold
So we've established that large and inefficient JS or CSS references in the <head>
of a page can delay the DOM parser from discovering nodes that get "painted" on the screen. This is a feature, not an undesirable behavior, because resources critical for rendering the initial view when the page is loaded (above-the-fold) should be downloaded and parsed before any nodes get painted.
Deciding whether to place either type of resource reference in the <head>
or within the <body>
depends on what is critical for displaying the initial view to the user.
As a general rule, JS/CSS resources critical for rendering above-fold content should be included within the <head></head>
HTML tags and prioritized.
These scripts and styles should already be downloaded and parsed before visually-rendered nodes get painted to the screen. We can guarantee this by referencing these resources in the <head></head>
of the site.
Conversely, by differing some JS and CSS we can reduce the time it takes for the DOM Parser to discover nodes that get visually rendered.
When to include JavaScript in the Head
- Tracking or analytics scripts. These scripts are typically lightweight and do not need access to the fully parsed DOM. These scripts provide important insights about the page and delaying these scripts till later will cause us to lose valuable tracking data.
- If used to render the above-the-fold content of the page. For example, JavaScript that creates elements that appear in the initial view. JS used for sliders might calculate the positioning of elements within the slider, so this JS should be loaded with the associated slider CSS in the
<head>
.
When to include JavaScript in the Body
- When the JS needs access to the fully parsed DOM. Instead of using the
DOMContentLoaded
,window.load
, orjQuery.ready()
events to call a function after the DOM loads, we can just place these scripts directly after the required HTML - When the JS is event-driven and below the fold. For example, JS that is triggered on click.
- When the JS isn't critical for rendering the layout. For example, you might have a section with rotating background-images, but the non-JS state is adequate and experience is enhanced after the JS loads.
When to include CSS in the Head
CSS for the above-the-fold content should always be included in the head. Otherwise, the HTML will render before styles are calculated. This causes a flash of broken content before the styles are calculated.
When to include CSS in the Body
Similarly to JavaScript, CSS can be deferred for content that is below the fold.
Reduce Parsing Time
The JavaScript and Stylesheet Parsing task duration primarily depends on the size of the file. Even if a page isn't using all of the code within a resource, a parser still extracts each line. The best way to reduce the parsing time is to use code splitting techniques to reduce unused code.