Claude Code for Partytown: Third-Party Scripts Off the Main Thread — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Partytown: Third-Party Scripts Off the Main Thread
Frontend

Claude Code for Partytown: Third-Party Scripts Off the Main Thread

Published: February 6, 2027
Read time: 7 min read
By: Claude Skills 360

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.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free