One of the first things we hear about JavaScript is that it is single-threaded and incapable of running tasks in parallel. But that is not entirely true, at least in the context of web browsers. As specified in HTML5 spec, Web Workers allow us to execute asynchronous code in background threads that runs independently of the main thread.
Very often, we need (or our client wants) to integrate a third-party service or functionality into our webpage that is not crucial to the main functionality of the page, such as sending a page-view to an analytics tool. Well, this comes with a downside: it increases the amount of time that our page needs to fully load, directly affecting our site performance. The result: when running a Lighthouse test, it will flag the high blocking time - screenshot below. The impact: Degraded user experience, which will impact the user and other aspects like SEO.This is where we can take advantage of Web Workers, but only if we use them in conjunction with Partytown.
Native Web Workers have their limitations, for example, they cannot access the DOM and can trigger cross-origin restrictions (I believe you too have encountered a CORS issue at least once). This is where Partytown comes in handy. It introduces a layer of abstraction that enables us to easily load any <script> element into our HTML code, providing DOM access and tools for proxying requests.
In this article, we are going to see how to initialize a Google Tag Manager (GTM) script in a separate thread using Partytown in a React application. This library was required as a set of integrations that our client requested. First step is to install Partytown itself.
Partytown provides us with a script for copying the necessary files that we need to serve to our page from our /public directory in order to use the library. This is important because these files must be served from the same origin. So, include it in our package.json
Tip: You can integrate this script into your build process directly as shown below
Although Partytown can be used in any webpage (you can refer to their Integration section), when working with React, we need to import the Wrapper Component for the Partytown Snippet and include it within the <head> tag of our page. I've done some testing and you can put it anywhere but that's their recommendation. You can archive this using a library like Helmet or, if you're using NextJS, within your <Head> tag (be sure to check their experimental API first).
If you're wondering about the forward={["dataLayer.push"]} property, this is because we need to do a little bit of configuration for some scripts to run as intended. In this case, Partytown requires us to specify which variables from the window object should be forwarded to the web worker. Since GTM calls window.dataLayer, by setting up this forward configuration, the web worker can still receive and process the data.
Here I'm using the Next.js Script component to initialize our scripts that will be executed by a web worker. We specify this by setting the type property of the script tag to "text/partytown".
In our case, users have the ability to add their custom tracking tags into their pages, so we need to add the tags dynamically. You can create multiple <Script/> elements with the src property, one for each tag you have. Then for the inlined script, you need to only repeat the last line, so you can create a function that generates it.
We can see how this impacted our main thread blocking time by running again a Lighthouse report, it tells us that we effectively initialized the script but it does not block the main-thread by any means. So we successfully reduced the impact of third-party code.
In the article we've discussed how Web Workers can be used to improve website performance by executing asynchronous code in the background. We exemplified it with a concrete, real-world problem: slow page loading caused by third-party scripts. We provide a detailed step-by-step guide on how to integrate Partytown into a React application to load scripts like Google Tag Manager in a separate thread. The results demonstrate a significant reduction in main thread blocking time, effectively improving the handling of third-party code and resulting in faster, smoother websites.