7 Ways to Minimize Main Thread Work

Last updated on Mar 15th, 2024 | 16 min

TL;DR: To minimize main-thread work and enhance website performance, focus on optimizing critical rendering paths by reducing JavaScript and CSS, prioritizing rendering of essential content first, using lazy loading for below-the-fold content, utilizing asynchronous loading for scripts that don't block rendering, and considering web workers to offload computationally intensive tasks to a separate thread.


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!


What Causes Main Thread Blocking?

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:

Unresponsive Web Page

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:

State of JavaScript

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.
 

Debugging Main Thread Issues

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.

Minimize main thread work

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.

Avoid Long tasks

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.

PSI INP

 

Total Blocking Time

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:

DevTools Capture Performance Coverage

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:

DevTools Performance

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 processing

WebPageTest also has a useful waterfall chart that marks render-blocking resources:

WaterFall Chart

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.


How to Minimize Main Thread Work

As you can see in PSI and WebPageTest, there are numerous categories of tasks that can overload the Main Thread:

  • Script evaluation;
  • Style and layout;
  • Parsing HTML and CSS;
  • Script Parsing and Compilation;
  • Garbage Collection.

Let’s take a closer look at each category and see what you can do to deal with the issues.  
 

Delay or Remove Third-Party JavaScript (Script Evaluation)

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:
 

  • Network requests - Sending too many requests to multiple servers (third-party) slowdowns. That time is even longer for secure connections, which may involve DNS lookups, redirects, and several round trips to the final server that handles the user's request.
     
  • Rendering - If the third-party JavaScript is rendered synchronously in the critical rendering path, it will delay the parsing of the rest of the document.

In fact, PSI has an audit that shows the impact of third-party code on a page:

Minimize third-party usage

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”:

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:

  • Scripts with the defer attribute keep their relative order. The browser doesn’t wait for them to render the page but does execute them in order. For example, say we have two scripts - script 1 and script 2 in that order. If we defer both, the browser will always execute script 1 first, even if script 2 was downloaded first. Deferred scripts are also executed before the DOMContentLoaded event. In other words, they run only after the HTML has been loaded and parsed.
     
  • Scripts with the async attribute are completely independent. Whichever loads first is executed first. They also run independently of the DOMContentLoaded event, i.e., they can be executed even if the document hasn’t been fully downloaded.

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.

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.

Unused JavaScript
 

JavaScript Execution with NitroPack

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:

Delayed JS

And specify the scripts that you want to load with a delay.


Use Web Workers (Script Evaluation)

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 and Removing Unused Code (Script Parsing and Evaluation)

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. 

Minify and Compress CSS and JavaScript (CSS & Script Parsing and Compilation) 

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.

Get your CSS and JS optimized automatically! Test your site with NitroPack →

Critical CSS (Parsing)

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. 

Above the fold content

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.

Important: You need to do this for every page on your website.  Also, don’t forget to take into account the different viewports (laptops, smartphones, tablets, etc.).


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.

Inlining CSS

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.

Unused CSS

In most cases, CSS that’s not used immediately after loading the page should be considered non-critical. 


Reduce Unused CSS (Parsing)

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.

Reduce Unused CSS

Again, this is essential for improving CSS parsing time, so it’s worth doing it with NitroPack or by hand.
 

Remove Unnecessary Plugins and Choose a Lightweight Theme (for WordPress Users)

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.
 

Replace all your site speed plugins with the best all-in-one solution! Test your site with NitroPack →

Recap and Going Beyond the Main Thread

We just covered a lot of ground, so let’s do a quick recap of all the techniques for minimizing Main Thread work:

  1. Delaying (via defer or async) or removing third-party scripts prevents them from blocking the page and speeds up script evaluation. Using web workers to run JS in a separate thread also helps in that regard.
  2. Code splitting and removing unused code both improve script parsing and compilation. They’re essential techniques, especially if your site is heavy on JS.
  3. Minification and compression are best practices for performance optimization. Minimize JS, CSS, and HTML to make the files lighter and easier to parse.
  4. Critical CSS and reducing unused CSS ensure that CSS doesn’t block the Main Thread, helping the browser render the page faster.
  5. Lastly, choosing a lightweight theme and removing unnecessary plugins is also crucial for WordPress users. Always run tests before and after installing a plugin/them.

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 Kaleev
Web Performance Geek

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.