Third-party scripts — analytics, tag managers, chat widgets, A/B testing tools — run on the main thread and compete with your UI code for CPU time. Partytown relocates these scripts to a web worker where they run off the main thread, keeping the main thread free for user interactions. The worker communicates with the main thread synchronously via a shared SharedArrayBuffer or atomics channel to access DOM APIs that third-party scripts require. A reverse proxy serves third-party scripts from your origin, enabling same-origin caching and avoiding cross-origin resource timing restrictions. Configuring forward tells Partytown which data layer pushes or function calls should be forwarded from the main thread to the worker. Claude Code generates Partytown configurations for Next.js, Astro, and vanilla HTML deployments, including reverse proxy rules and the forward array setup for common analytics stacks.
CLAUDE.md for Partytown
## Partytown Stack
- Version: @builder.io/partytown >= 0.10
- Next.js: <Partytown /> in _document or app/layout — type="text/partytown" on <Script>
- Astro: @astrojs/partytown integration — partytownConfig in astro.config.mjs
- Forward: forward array for dataLayer.push, gtag, fbq, analytics — intercepts calls
- Proxy: next.config.js rewrites or nginx for /~partytown/ to vendor scripts
- Debug: debug: true + logCalls: true in dev to trace worker calls
- SharedArrayBuffer: requires COOP/COEP headers — or use atomics fallback
Next.js Integration
// app/layout.tsx — Partytown in Next.js App Router
import { Partytown } from "@builder.io/partytown/react"
import Script from "next/script"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<Partytown
debug={process.env.NODE_ENV === "development"}
forward={[
// Forward data layer pushes to GA4
"dataLayer.push",
// Forward Intercom calls
"Intercom",
// Forward Facebook Pixel
"fbq",
// Forward custom analytics
"analytics.track",
"analytics.identify",
]}
// Proxy vendor scripts through your origin for better caching
resolveUrl={(url, location) => {
if (url.hostname === "www.googletagmanager.com") {
const proxyUrl = new URL(`${location.origin}/~partytown/proxy`)
proxyUrl.searchParams.set("url", url.href)
return proxyUrl
}
// Serve GA4 through proxy
if (url.hostname === "www.google-analytics.com") {
const proxyUrl = new URL(`${location.origin}/~partytown/proxy`)
proxyUrl.searchParams.set("url", url.href)
return proxyUrl
}
return url
}}
/>
</head>
<body>
{children}
{/* Google Tag Manager — runs in Partytown worker */}
<Script
id="gtm-init"
type="text/partytown"
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${process.env.NEXT_PUBLIC_GTM_ID}');
`,
}}
/>
{/* Facebook Pixel — runs in worker */}
<Script
id="fb-pixel"
type="text/partytown"
dangerouslySetInnerHTML={{
__html: `
!function(f,b,e,v,n,t,s){
if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${process.env.NEXT_PUBLIC_FB_PIXEL_ID}');
fbq('track', 'PageView');
`,
}}
/>
</body>
</html>
)
}
Next.js Proxy Rewrite
// next.config.ts — proxy third-party scripts through origin
import type { NextConfig } from "next"
const nextConfig: NextConfig = {
async rewrites() {
return [
// Proxy GTM through your origin
{
source: "/~partytown/proxy",
destination: "https://www.googletagmanager.com/gtm.js",
},
// Proxy GA4
{
source: "/~partytown/ga4",
destination: "https://www.google-analytics.com/g/collect",
},
]
},
async headers() {
return [
{
// Required headers for SharedArrayBuffer (Partytown atomics mode)
source: "/(.*)",
headers: [
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
],
},
]
},
}
export default nextConfig
Astro Integration
// astro.config.mjs — Partytown for Astro
import { defineConfig } from "astro/config"
import partytown from "@astrojs/partytown"
export default defineConfig({
integrations: [
partytown({
config: {
debug: import.meta.env.DEV,
forward: ["dataLayer.push", "gtag", "fbq"],
resolveUrl(url, location) {
// Proxy GTM through Astro origin
if (url.hostname === "www.googletagmanager.com") {
const proxyUrl = new URL("/gtm-proxy", location)
proxyUrl.searchParams.set("url", url.href)
return proxyUrl
}
return url
},
},
}),
],
})
---
// src/pages/_document.astro — scripts with partytown type
---
<!-- GA4 config runs in worker -->
<script type="text/partytown" src={`https://www.googletagmanager.com/gtag/js?id=${Astro.locals.gaMeasurementId}`}></script>
<script type="text/partytown">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXX');
</script>
Event Forwarding from React
// src/lib/analytics.ts — forward main thread events to Partytown worker
// These run on main thread but are forwarded via 'forward' config
declare global {
interface Window {
dataLayer: unknown[]
gtag: (...args: unknown[]) => void
fbq: (...args: unknown[]) => void
}
}
export function trackPageView(url: string) {
// gtag runs in worker — forwarded via forward: ["gtag"]
window.gtag?.("config", process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!, {
page_path: url,
})
}
export function trackEvent(
eventName: string,
params?: Record<string, unknown>
) {
// Forwarded to worker
window.gtag?.("event", eventName, params)
// Also push to dataLayer for GTM — forwarded via forward: ["dataLayer.push"]
window.dataLayer?.push({
event: eventName,
...params,
})
}
export function trackPurchase(order: {
id: string
revenue: number
items: { id: string; name: string; price: number }[]
}) {
window.gtag?.("event", "purchase", {
transaction_id: order.id,
value: order.revenue,
currency: "USD",
items: order.items.map(item => ({
item_id: item.id,
item_name: item.name,
price: item.price,
})),
})
}
Measuring Main Thread Relief
// scripts/measure-tbt.ts — before/after TBT measurement guidance
// Use Lighthouse CLI or Chrome DevTools Performance panel
// Before Partytown: measure Total Blocking Time
// Run: npx lighthouse https://yoursite.com --only-metrics=total-blocking-time --output=json
// Key metrics to track:
// - Total Blocking Time (TBT): target < 200ms
// - Interaction to Next Paint (INP): target < 200ms
// - Long Tasks on main thread: target 0 tasks > 50ms from third parties
// In Chrome DevTools Performance recording:
// Look for main thread tasks during page load
// Third-party scripts should appear in Worker thread after Partytown
console.log(`
Partytown moves third-party scripts to a worker thread.
To measure improvement:
1. Record baseline: Chrome DevTools > Performance > Record page load
2. Install Partytown and configure third-party scripts
3. Record again and compare:
- Total Blocking Time in Lighthouse
- Long tasks attributed to third-party scripts
- Main thread CPU usage during load
Expected improvement for sites with many tag manager tags:
- TBT reduction: 200-1000ms
- INP improvement: third-party polling no longer blocks interactions
`)
For the no-partytown alternative of simply loading analytics in a standard Web Worker using postMessage for DOMless tracking scripts like segment.io, the custom worker pattern provides more control without Partytown’s proxy complexity. For the server-side tagging alternative where GTM runs on your server rather than in the browser at all — eliminating client-side script entirely — the edge analytics patterns cover server-side event collection. The Claude Skills 360 bundle includes Partytown skill sets covering Next.js integration, proxy configuration, and event forwarding. Start with the free tier to try Partytown configuration generation.