DEV Community

Alex Spinov
Alex Spinov

Posted on

Partytown Has a Free Library That Moves Third-Party Scripts Off the Main Thread

Google Analytics, Facebook Pixel, HubSpot, Intercom — your page loads 500KB of third-party JavaScript that blocks rendering. Partytown moves it all to a Web Worker, freeing your main thread.

The Problem

Third-party scripts are the #1 cause of poor Core Web Vitals:

  • Google Tag Manager: 100-300ms blocking time
  • Analytics scripts: 50-200ms each
  • Chat widgets: 200-500ms
  • Ad tracking: 100-400ms

Total: 500ms-1.5s of main thread blocking before your app becomes interactive.

How Partytown Works

Main Thread (your code)     Web Worker (3rd party scripts)
┌──────────────────┐        ┌──────────────────────┐
│ Your React app   │        │ Google Analytics      │
│ User interactions│  ←───→ │ Facebook Pixel        │
│ Smooth 60fps     │  proxy │ HubSpot tracking      │
│                  │        │ Intercom widget       │
└──────────────────┘        └──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Partytown proxies DOM access from the worker thread. Third-party scripts run as if they are on the main thread but they are actually isolated.

Setup (Next.js)

npm install @builder.io/partytown
Enter fullscreen mode Exit fullscreen mode
// app/layout.tsx
import { Partytown } from "@builder.io/partytown/react";

export default function Layout({ children }) {
  return (
    <html>
      <head>
        <Partytown forward={["dataLayer.push", "gtag"]} />
        <script
          type="text/partytown"
          dangerouslySetInnerHTML={{
            __html: `
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag(js, new Date());
              gtag(config, G-XXXXXXX);
            `,
          }}
        />
        <script
          type="text/partytown"
          src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

The key: change type="text/javascript" to type="text/partytown". That is it.

Setup (Astro)

// astro.config.mjs
import { defineConfig } from "astro/config";
import partytown from "@astrojs/partytown";

export default defineConfig({
  integrations: [
    partytown({
      config: {
        forward: ["dataLayer.push", "gtag"],
      },
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
Enter fullscreen mode Exit fullscreen mode

Results

Typical improvements:

  • Total Blocking Time (TBT): -60 to -80%
  • Largest Contentful Paint (LCP): -200 to -500ms
  • First Input Delay (FID): near zero
  • Lighthouse Performance: +15 to +30 points

What Scripts to Move

Safe to move (tracking/analytics):

  • Google Analytics / GA4
  • Google Tag Manager
  • Facebook Pixel
  • HubSpot tracking
  • Segment
  • Mixpanel
  • Hotjar

Keep on main thread (needs DOM interaction):

  • Chat widgets (Intercom, Drift) — if they need to show UI
  • Payment forms (Stripe Elements)
  • reCAPTCHA

The forward Config

Partytown needs to know which global functions to proxy:

forward: [
  "dataLayer.push",   // GTM
  "gtag",             // GA4
  "fbq",              // Facebook Pixel
  "_hsq.push",        // HubSpot
]
Enter fullscreen mode Exit fullscreen mode

Need performance optimization for your web app? I build fast web solutions and data tools. Email spinov001@gmail.com or check my Apify tools.

Top comments (0)