Third-Party Scripts in Service Worker
Move third-party analytics and tracking scripts off the main thread by running them in a service worker.
Metrics
This technique does not move LCP.
This technique can move up to 100% of TBT's share of the Lighthouse score.
This technique does not move CLS.
This technique can move up to 33% of FCP's share of the Lighthouse score.
This technique can move up to 33% of SI's share of the Lighthouse score.
This technique does not move TTFB.
Overview
A service worker is a JavaScript worker that runs in a background thread, separate from the browser’s main thread. Third-party scripts that do not interact with the DOM — analytics trackers, conversion pixels, A/B testing data collectors, and session recorders — can be proxied through a service worker to remove their execution overhead from the main thread entirely. The technique is sometimes called ‘party town’ after the Partytown library, which automates the migration.
Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: LCP, CLS, TTFB.
The Problem
Third-party analytics and marketing scripts collectively represent the largest non-controllable source of main thread blocking on most marketing-oriented WordPress sites. Google Tag Manager, Facebook Pixel, LinkedIn Insight, Hotjar, Intercom, and similar tools download and execute JavaScript that occupies the main thread for 50–400 ms each. Combined, a typical marketing site may load 8–15 such scripts, collectively blocking the main thread for 2–5 seconds. This directly inflates Total Blocking Time and degrades Interaction to Next Paint scores.
Performance Impact
TBT
20–60%
third-party script execution moved off main thread eliminates their contribution to long tasks
FCP
Indirect
less main-thread competition during initial render benefits first contentful paint
SI
Indirect
earlier FCP due to freed main thread contributes to faster visual fill
INP
Significant
main thread is substantially more available for interaction processing when 3P scripts run in worker
Risk: Medium
DOM-dependent scripts are incompatible; requires specific COOP/COEP HTTP headers that may break third-party iframes
How it works
01
Service workers run in a separate thread
Service workers execute in a background thread that cannot directly access the DOM. They communicate with the main thread via the
postMessageAPI. For analytics scripts that only send data to external servers (they read cookies and properties, then fire XHR/fetch requests), this communication overhead is negligible.02
Partytown proxies DOM access
The Partytown library solves the DOM access problem by using a synchronous-looking proxy: when a third-party script in the worker calls
document.cookie, Partytown serializes the call, sends it to the main thread viapostMessage, returns the result synchronously (viaAtomics.wait), and returns control to the worker. Scripts see a real DOM interface while executing entirely off-thread.03
Identify compatible scripts
Scripts that only read from the DOM and send data to external APIs are ideal candidates: Google Analytics 4, Facebook Pixel, LinkedIn Insight Tag, HubSpot, and Segment. Scripts that manipulate the DOM (injecting banners, modifying page content) are incompatible and must remain on the main thread.
04
WordPress integration via script_loader_tag
For each compatible third-party script, change its
typeattribute totext/partytown. Partytown intercepts script tags with this type and routes them to the worker. In WordPress, this is done via thescript_loader_tagfilter.
Code Example
<!-- Google Tag Manager: executes on main thread, blocks interaction -->
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX"></script><!-- type="text/partytown" routes execution to service worker -->
<script type="text/partytown" src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX"></script>
<!-- Partytown config (add before partytown.js) -->
<script>
partytown = {
lib: '/wp-content/themes/my-theme/js/~partytown/',
forward: ['dataLayer.push'] // forward GTM dataLayer calls from main thread to worker
};
</script>
<script src="/wp-content/themes/my-theme/js/~partytown/partytown.js"></script>Download Partytown via npm: npm install @builder.io/partytown. Copy the files from node_modules/@builder.io/partytown/lib to your theme’s JS directory. Configure the lib path to point to that directory.
Limitations
- Scripts that modify the DOM — injecting HTML, adding event listeners to elements, or reading element sizes — cannot run in a service worker and must remain on the main thread.
- Partytown’s synchronous DOM proxy uses
Atomics.wait, which requiresSharedArrayBuffer. This requires specific HTTP response headers:Cross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: require-corp— which may break third-party iframes (ads, embeds). - Debug complexity increases: scripts running in a worker are harder to inspect in DevTools. Errors in proxied scripts may appear with less context than main-thread errors.
Common Mistakes
Moving GTM to a service worker without forwarding the dataLayer
If your main-thread code pushes events to dataLayer (e.g., from button click handlers), those calls must be forwarded to the worker where GTM is running. Configure Partytown’s forward option to include all calls that cross the thread boundary.
Assuming all analytics scripts are compatible without testing
Test each script individually in Partytown’s sandbox before deploying to production. Conversion tracking scripts that read from the DOM (e.g., Hotjar session recording) are typically incompatible and will produce silent failures — events stop firing with no visible error.