Optimizing JavaScript Priority for Faster Page Loads
Learn how to prioritize scripts effectively to boost Core Web Vitals.
Managing JavaScript Priorities for Better Web Performance
One thing has always been clear: not all JavaScript is created equal. Some scripts handle critical interactions like 'menu interaction' or 'add to cart' while other scripts are far less important. Let's take your 'exit intent' popup script that invites visitors who are about to leave your site to fill out a questionnaire. I am sure we could all live without the last ones but it would be really hard to navigate a website without the first one.
Yet, on 'your average website' on a technical level this distinction is hardly ever made. All JavaScripts are 'just added' to the page and the browser is left to figure it out. Now that is a problem because your browser has n idea what is important and what is not. We, as developers do. So let's fix that!
How JavaScript priority can impact the Core Web Vitals
Just adding scripts to the page without the right consideration can impact all 3 of the Core Web Vitals. The Largest Contentful Paint, the Interaction to Next Paint and the Cumulative Layout Shift.
Example: the LCP network resource is delayed by render blocking JavaScripts
The Largest contentful Paint is prone to bandwidth and CPU competition. When too many scripts fight for early network resources it will delay the Largest Contentful Paint network resource and early CPU work will delay the LCP by blocking the main thread.
The Interaction to Next Paint can be affected by scripts executing right before an interactions. When scripts execute they block the main thread and wil delay any interaction during that execution time.
Scripts can also cause a Cumulative Layout Shift if scripts 'change how the page looks'. Ad scripts that inject banners into the page and sliders are notorious for doing this.
5 Types of JavaScript priority's
I like to distinguish between 5 types of JavaScript priorities. Let's quickly discuss those before we dig in.
- Render Critical: these scripts are among the worst to have. They change the layout of the page and without loading these scripts the lay-out will be completely different. Example: some slider scripts or and A/B test.
- Critical Scripts: These scripts handle critical page functionality and without these scripts critical tasks like adding a product to a cart, site search or navigation is not possible.
- Important scripts. These scripts handle important (business) logic and your site depends on these. For example: Analytics
- Nice to have scripts. These scripts are nice to have but if push comes to shove we do not really need them for the page to function. For example a chat widget or an exit intent
- Future Scripts. These scripts might be critical or nice to have but we do not need them right now because 'other steps' need to be taken before we can actually use these scripts. For example a multi-stage check out script.
1. Render-Critical Scripts
These are the most disruptive scripts, as they directly impact how the page is displayed. Without them, the layout may break or appear drastically different from its intended design. Examples include scripts for sliders or A/B testing frameworks that alter the layout early in the loading process.
The problem with these types of script is that they can not be deferred or delayed. Any delay will cause the website lay-out to shift causing a poor UX and the Core Web Vitals to fail.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Page Title</title>
<link href="styles.css" rel="stylesheet" />
<script src="render-critical.js"></script>
</head>
<body></body>
</html>
Best Practices:
- Avoid render critical scripts like this whenever possible. Rewrite your code to avoid dependance on these types of scripts.
- If there is no avoiding it inline or load only the absolutely necessary parts of these scripts.
- Do not defer or async these scripts and place them at in the top of the head to trigger an 'as early as possible' download.
2. Critical Scripts
These scripts enable fundamental interactions. Without them, critical tasks like site navigation, adding items to a cart, cookie notice or performing a search become impossible. They are indispensable for the site's core functionality.
These scripts should be placed in the head of the page with either the async or defer attribute.
<script defer src="critical.js"></script>
<script async src="critical.js"></script>
Best Practices:
- Keep scripts like these to a minimum and do not combine this functionality with other, less critical functionality.
- Load these scripts early using
async
ordefer
, depending on their dependencies. - Use Real User Monitoring (RUM) tools, such as Coredash, to identify bottlenecks in execution and ensure their performance aligns with user needs.
3. Important Scripts
While not directly tied to the site's usability, important scripts support key business functions. Analytics scripts, for example, provide essential data but do not need to load before more important visual elements. Obviously the distinction between critical and important scripts can be a matter of debate so be sure to task to all stakeholders before setting this priority!
There are 3 ways to lower the script priority for these types of scripts.
<html>
<head>
<!-- method 1: low fetchpriority -->
<script fetchpriority="low" defer src="important.js"></script>
<!-- method 2: inject after DOMContentLoaded -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var script = document.createElement('script');
script.src = 'important.js';
document.body.appendChild(script);
});
</script>
</head>
<body>
<!-- method 3: place at the bottom of the page -->
<script defer src="important.js"></script>
</body>
</html>
1. Low fetchpriority.
Setting the fetchpriority will lower the relative priority of the script. Other deferred or asynced scripts will likely be queued with a high priority while the scripts with fetchprioriy="low"
will be queued with a low priority. Dpending on your page (or your rendering path) this might be enough to prioritize other resources like your Largest Contentful Paint image and important fonts.
2: Inject after DOMContentLoaded
By injecting the script after the DOMContentLoaded event, you ensure the script starts downloading directly after the HTML has been fully parsed. This allows discoverable resources, such as images and fonts, to take precedence. This method provides a balance: the script begins loading early enough to avoid delays in functionality but doesn’t compete with early resources that are crucial to initial page rendering.
3: place at the bottom of the page
This classic technique defers script loading until after the browser has processed the entire document and achieves roughly the same result as technique. The only difference is that technique 2 skips your browsers preload scanner while this technique does not. The preload scanner is a lightweight quick scanner that your browser uses to quickly identify and enqueue critical resources.Skipping the preload scanner might be a good idea if there is a possibility of lazy loaded images in the viewport while using the preload scanner will speed up loading for this script.
4. Nice-to-Have Scripts
These scripts enhance the user experience but are not required for the site to function. Examples include chat widgets, customer feedback popups, or optional animations. While beneficial, they should not interfere with the primary user experience.
These scripts are a n ideal candidate to load with a pattern called 'lazy on load'. This means wait for the page it's load event and then, during idle time, inject the script. Waiting for the load even ensures the script does not compete for bandwidth and CPU with more important early resources. Waiting for an idle moment ensures the browser is not handling more important tasks like user input.
Here is a working example:
window.addEventListener("load", () => {
window.requestIdleCallback(() => {
const script = document.createElement("script");
script.src = "/path/to/script.js";
document.head.appendChild(script);
});
});
Best Practices:
- Lazy-load these scripts after the page has loaded and wait for an idle moment.
- Understand that scripts loaded with this pattern are not guaranteed to load fast
5. Future Scripts
Future scripts are those that won’t be needed until specific conditions are met. For example, a multi-stage checkout script becomes relevant only after a user has added items to their cart. These scripts can often wait until much later in the user’s journey.
Take a look at this example. It uses the intersection observer to load the JS logic required for the sign-up script only when the form is in the visible viewport.
<!DOCTYPE html>
<html>
<head>
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.querySelector("form");
const observer = new IntersectionObserver(function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const script = document.createElement("script");
script.src = "/sign-up.js";
document.head.appendChild(script);
observer.unobserve(form);
}
});
});
observer.observe(form);
});
</script>
</head>
<body>
<form action="/sign-up" method="post">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
<button type="submit">Sign Up</button>
</form>
</body>
</html>
Best Practices:
- Load these scripts on demand, triggered by user actions.
- Use code-splitting techniques to deliver only the parts required at each step.
- Dynamically inject them only when needed, such as when a user scrolls to a specific section.
Need your site lightning fast?
Join 500+ sites that now load faster and excel in Core Web Vitals.
- Fast on 1 or 2 sprints.
- 17+ years experience & over 500 fast sites
- Get fast and stay fast!