I quickly realized, though, that improving my Dev Tools proficiency meant first solidifying my understanding on the browser; specifically, the Critical Rendering Path.
You have to know what you're looking for, in order to analyze it, right? =)
Critical Rendering Path
Let's take a high level look at the Critical Rendering Path. (If you want to dig deep into the full path, check out Google's Web Fundamentals.)
The Critical Rendering Path can be separated into two main portions: network transmission, and browser processing.
Render Processing: Involves the processing steps to turn these assets into rendered pixels (first paint).
In order to render a page, the web browser needs to receive the HTML and CSS, and respectively build the Document Object Model (DOM) and the CSS Object Model (CSSOM). Thus, both HTML and CSS are render blocking resources.
In addition to render blocking HTML and CSS, by default (without the
Breaking Down the Critical Rendering Path
Building the DOM and CCSOM
After the HTML is received, the browser begins parsing the received bytes to create the Document Object Model DOM.
Bytes → Characters → Tokens → Nodes → Document Object Model
While parsing the HTML, if the browser comes across an external resource reference, the browser will send off a network request to fetch the file. For example, when the browser comes across a link tag
<link href="style.css" rel="stylesheet">, it will send off a network request to retrieve the CSS stylesheet .
When the CSS file is received, the same parsing process happens on the CSS file to create the CSS Object Model.
Bytes → Characters → Tokens → Nodes → CSS Object Model
When the browser comes across an inline
This parser blocking behaviour is detrimental to user-experience, since a network roundtrip could add thousands of milliseconds of delay to the critical rendering path!
Furthermore, another important behaviour to be aware of is that script execution will only begin after all prior referenced CSS resources have been downloaded and their CCSOM has been constructed.
Basically, script execution is itself blocked until the CCSOM has been constructed.
Optimizing the Critical Rendering Path
Essentially, optimizing the Critical Rendering Path involves minimizing the total amount of time spent performing each step in the render path:
- Processing HTML markup and building the DOM tree
- Processing CSS markup and building the CSSOM tree
- Combining the DOM and CSSOM into a render tree
Optimizing the CSSOM and the complexity of style calculations has it's own focus. I'll just be looking at optimizing script loading in this article.
Optimizing Script Loading
Okay, that was a lot of background info - let's dig into my take-aways on optimizing script loading.
Baseline - Regular Script Loading:
<script src="first.js"></script> <script src="//domain.com/second.js"></script>
In this situation, the browser will block the parser while it downloads both scripts in parallel. The scripts will then be executed in order of declaration in the HTML.
second.jswon’t execute until
first.jswon’t execute until the CSSOM has been fully parsed, and until any previous scripts are executed.
- The Critical Rending Path is blocked while all this is happening.
Comments: This is the least optimal method of loading scripts.
Even if we try to prevent blocking the DOM by placing the
<script> at the end of the document, right before the
</body> tag, the browser won't fetch the script until the whole document has been parsed.
Ideally, we would like to begin fetching these scripts asynchronously, as soon as possible, and then execute them when they're received across the network.
Asynchronously Load Scripts with
<script src="first.js" async></script> <script src="//domain.com/second.js" async></script>
In this situation, the the
async attribute tells the browser that the script is not part of the critical rendering path. The browser downloads both scripts in parallel, but the parser continues to construct the DOM while the resources are being fetched.
As soon as a script file is received across the network, it will be executed in the order it is received. This can introduce dependency issues as the order of declaration is not maintained!
- The browser parser is not blocked while scripts are fetched.
- Scripts will execute as soon as it is retrieved, regardless of the order it was defined in the document.
- Scripts do not wait for the CSSOM to be fully parsed.
async attribute is the best practice as it allows script references to be placed in the
<head>, so that they can be fetched early on and asynchronously.
async scripts still block the browser parser during script execution. So a long-running script could still potentially delay the first paint.
Defer Script Loading with
In this situation, the the
defer attribute not only tells the browser to prevent the parser from being blocked while the scripts are being fetched, but to also delay script execution until after the document has been parsed (just before
domContentLoaded is fired).
All deferred scripts will also maintain their declaration order.
<script src="first.js" defer></script> <script src="//domain.com/second.js" defer></script>
The best uses for
defer are for any non-critical scripts, like analytics, ad networks, social networks, etc. that don't provide any core functionality to your website / app.