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.
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:
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:
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.
We’ll take the time to discuss that later on.
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.
As far as the field data (i.e., real-user metrics) is concerned, you want to focus on the First Input Delay metric.
These three metrics all 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.
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.
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 Settings → JS Settings.
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.
For a deeper dive into the topic, check out Surma’s article on the state of web workers in 2021.
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.
Code minification and compression are both best practices for page speed optimization.
Minification 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.
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.
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.
Evgeni writes about site speed and makes sure everything we publish is awesome.