HTML, CSS, and JavaScript parsing, construction of the Document Object Model (DOM), adding computed styles, producing the layout tree, creating paint records, responding to user interactions.
These are just some of the steps browsers take to turn code into interactive web pages. And all of them are handled by the Main Thread.
Put simply:
The Main Thread is where the browser does most of the work needed to display a page.
If we keep the Main Thread blocked, it can’t perform its crucial tasks. This leads to slow load times, unresponsive pages, and a bad user experience.
That’s why minimizing Main Thread work lets the browser, paint pixels on the screen faster and be more responsive to the user.
And in this article, you will learn how to do that.
Let’s dive in!
Plain and simple:
Long-running JavaScript.
JavaScript is an extremely expensive resource, and usually, it’s the main villain when it comes to Main Thread blocking.
By default, all JavaScript runs on the Main Thread. Some people have compared this behavior to the 9am rush hour traffic, where everything drives on one road (the Main Thread).
And when the main road (thread) is blocked, people have a bad time. In the context of online user experience, this translates to rage-clicking buttons, low-quality animations, and laggy scrolling.
But maybe the worst part is this popup:
Any way you slice it, the Main Thread is overworked and underpaid:
Unfortunately, things don’t seem to be getting better. Data from the HTTP Archive shows that JavaScript bytes and JavaScript requests have done nothing but go up since 2010:
And the biggest problem is that JavaScript can come from many different places - Dev frameworks, CMS themes, and plugins.
That’s why it’s so important to test your site speed before and after installing a new plugin or theme. Try to keep your site as lean as possible. I know that some animations or widgets add an extra flair, but their performance costs might be very high.
Besides JavaScript, CSS also blocks the Main Thread by default. While it’s usually not as severe as JavaScript, you still need to prevent render-blocking CSS.
We’ll take the time to discuss that later on.
Google’s PageSpeed Insights (PSI) is a great debugging tool. It has different audits about the Main Thread and JavaScript usage.
The “Minimize main-thread work” audit is a good starting point as It analyzes Main Thread activities and groups them into tasks.
We’re going to examine each task in further detail and offer you ways to tackle every issue.
The “Avoid long main-thread tasks” audit lets you get more specific by showing how long specific JavaScript tasks take to run. Long tasks are those that take more than 50ms to run, and they are the main reason browsers showing the “Kill page” popup.
Besides these audits, PSI’s interactivity metric also indicates whether a page has a Main Thread problem. In the lab, Total Blocking Time (TBT) measures the impact of Long Tasks and unoptimized JavaScript.
As far as the field data (i.e., real-user metrics) is concerned, you want to focus on Interaction to Next Paint (INP).
These two metrics deal with interactivity, so you likely have a Main Thread problem if they’re in the red.
A great way to analyze the issues even deeper is using Chrome’s DevTools. Open a web page, right-click and select “Inspect”. Go to “Performance” and capture the page load. The “Coverage” panel is also useful here:
Once the report is ready, go to the “Main” section and look for grey tasks with a small red overlay. Those are the Long Tasks. You can also use the “Coverage” tab to find specific files and see what % of the JS code in them remains unused:
WebPageTests’ processing breakdown is another useful tool for debugging Main Thread issues. It visualizes how much time was spent on categories of tasks like scripting, layout, and painting. It also breaks down how long specific events from each category took:
WebPageTest also has a useful waterfall chart that marks render-blocking resources:
As you already know, when the browser encounters these resources, it has to download, parse and execute each one before doing anything else. They block the Main Thread. That’s why serving render-blocking resources in succession usually leads to slow load times.
Now, let’s see how to deal with these issues.
As you can see in PSI and WebPageTest, there are numerous categories of tasks that can overload the Main Thread:
Let’s take a closer look at each category and see what you can do to deal with the issues.
Third-party JavaScript can come from many different places like plugins, tools, and frameworks.
For example, popular advertising and analytics tools like Google Analytics, Google Ads, Facebook Pixel. Or video player embeds, chat services, and advertising iframes.
And the list goes on.
Any significant amount of JavaScript can slow down web performance. But the problem with third-party JavaScript is that it's outside your control. This can bring additional issues:
In fact, PSI has an audit that shows the impact of third-party code on a page:
The audit shows both transfer size and Main Thread blocking time.
You can also use DevTools to block these third-party scripts and see how the page loads without them.
Open DevTools, go to the “Network” panel and find the resource in question. Right-click and select “Block request URL”:
Hopefully, you should see a speed improvement. However, if you don’t see much difference, reduce the list of blocked URLs until you find the one(s) causing the delay.
Once located, a defer or async attribute should be added to these scripts. Both attributes make scripts non-blocking, which reduces their impact. However, they also have important differences:
Because of these differences, scripts that need the DOM or whose order is important should use the defer attribute. Conversely, ad, analytics, and other independent scripts should generally use async.
It’s also important to check for unused third-party scripts and remove them. This can happen if you haven't removed all the code from tools you’re no longer using.
Again, use the “Coverage” tab in DevTools to find these code snippets.
If you’re using NitroPack, the issues we just went over are already resolved for you.
Our service delays the loading of non-critical resources until user interaction is detected. We also have a proprietary resource loader that rearranges the way resources are fed to the Main Thread. We do this to take advantage of the modern CPU’s multi-core nature by offloading tasks away from the Main Thread.
You also have the option to specify which scripts should be loaded with a delay.
You can find this feature by going to Cache Settings → Javascript.
Then, scroll down until you see the “Delayed Scripts” feature:
And specify the scripts that you want to load with a delay.
Another way to improve script evaluation and boost your site’s performance is to use web workers. Web workers let you execute code in a separate thread, which reduces the impact on the Main Thread.
There are different ways to do this, which fall under the umbrella term “Off the Main Thread (OMT) Architecture.”. In general, moving non-UI operations away from the Main Thread is a good practice. Android and iOS even refer to the Main Thread as the UI thread.
However, that’s easier said than done. While all major browsers support them, they aren’t easily accessible to most website owners since they require a deep technical understanding of how the web and JavaScript work.
For a deeper dive into the topic, check out Surma’s article on the state of web workers in 2021.
Code splitting refers to splitting up your JavaScript bundles and sending only what’s necessary at the very beginning. This prevents the Main Thread from getting overwhelmed as a lot of the JavaScript on the page is served on demand.
JavaScript module bundles like Webpack and Rollup either split code into chucks automatically or provide easy ways to do so. Some popular frameworks like Next.js and Gatsby even have Webpack setup by default.
Phil Walton (engineer at Google) has a similar strategy, which he calls Idle Until Urgent.
What we said about removing unused third-party scripts also goes for your own JS. You can use DevTools to find unused JS, as shown in the screenshots above.
Refactoring your website’s code is where it gets rough. This takes more effort and specialized skills, but the performance gains can be massive.
Even a single line of inefficient JavaScript code can make the site significantly slower or even non-responsive. That’s why cleaning up your site’s JavaScript code can speed it up big time.
Code minification and compression are both best practices for page speed optimization.
Often used interchangeably, minify JS (or minimize JS) removes unnecessary elements from code files like comments, whitespace, and line breaks.
On the other hand, compression rewrites the files’ binary code, using fewer bits than the original. This is done by applying different compression algorithms.
Along with minimize CSS and minimize HTML, these are great techniques to speed up code parsing.
Some hosting companies apply these techniques by default, so it’s worth checking with your provider.
Then, you can use DevTools to see if files are minified and compressed. Minified files typically have “.min” in their name. Compressed files have a content-encoding header, usually with a gzip or br value.
Critical CSS is the CSS applied to above-the-fold elements. In other words, it’s responsible for the content that’s immediately visible to your users.
Critical CSS is a technique that involves three steps:
#1: Find the CSS that’s responsible for styling above the fold content on different viewports.
You can accomplish this by going through your page’s Document Object Model (DOM) and considering the style applied to it.
Then, you’d have to set up Critical CSS rules for each viewport separately.
#2: Inline it in the page’s head tag.
External CSS stylesheets are the industry standard. They're easier to maintain and more convenient to work with. However, the browser has to download, parse and execute all of them before rendering the rest of the page.
And that’s why CSS is a render-blocking resource.
By inlining the Critical CSS in the head tag of the HTML file, you eliminate the need for the browser to make an additional request to fetch these styles.
As a result, you will experience improved render times.
#3: Defer the rest of the CSS.
The rest of the CSS (the external one) can then be loaded asynchronously.
If you don’t know how to do that, you can follow the steps presented in this article on Deferring non-critical CSS.
Ultimately, the result of applying the Critical CSS technique is that the browser quickly finds, parses, and executes the CSS responsible for above the fold content. That can help improve the UX significantly, as users see content immediately.
The Main Thread is also not as busy since there’s less CSS to parse at one time.
If you’re using NitroPack, Critical CSS is enabled for each page on your site by default.
If you want to implement this technique by hand, DevTools offers a way to distinguish between critical and non-critical CSS. We showed the technique earlier when talking about JS, but it also works for CSS.
In most cases, CSS that’s not used immediately after loading the page should be considered non-critical.
Similar to Critical CSS, reducing unused CSS makes the browser's job easier.
That’s why we built the Reduce Unused CSS (RUCSS) feature for NitroPack. It works by finding CSS rules that aren’t used on the page and removing them.
For example, global CSS files usually have thousands of rules. If you’re on the home page, you don’t need the CSS that styles blog posts and vice versa. But because the rules are in a global stylesheet, the browser has to deal with them, regardless if they contribute anything to the page.
The RUCSS feature finds and reduces these unnecessary CSS rules. This directly affects how fast the browser renders the page.
Again, this is essential for improving CSS parsing time, so it’s worth doing it with NitroPack or by hand.
If you're using WordPress, you should carefully consider your theme and each plugin you add.
Many themes and plugins have a ton of JS baked into them. This makes it impossible to optimize your site speed without changing the theme or removing the plugin in question.
There are lists of slow plugins and lightweight themes online, but you should definitely do your own research and see how each add-on affects your site’s performance.
We just covered a lot of ground, so let’s do a quick recap of all the techniques for minimizing Main Thread work:
Some of these optimizations can be automated, but others require digging into your website’s code and refactoring it. Again, while challenging, it can bring great results, and sometimes, that might be the only solution for JS-heavy websites.
If you minimize the Main Thread’s load, you’re well on your way to a fast website. Two other areas you should consider improving are image optimization and Time to First Byte.
Niko has 5+ years of experience turning those “it’s too technical for me” topics into “I can’t believe I get it” content pieces. He specializes in dissecting nuanced topics like Core Web Vitals, web performance metrics, and site speed optimization techniques. When he’s taking a breather from researching his next content piece, you’ll find him deep into the latest performance news.