起因
你的网站有:
- Google Analytics
- Google Tag Manager
- Facebook Pixel
- HubSpot / Intercom chat
- Segment
第三方 script 通常 200-500 KB 不少,跑在 main thread → 占 CPU + 拖慢
首屏(hydration / interaction)。
Lighthouse 性能跌 20-30 分常因这些。
Partytown(Builder.io):把第三方 script 丢到 web worker 跑。
main thread 专心 render,性能不被第三方坑。
原理
Main thread:
- 你的 React / Vue
- DOM 操作
- 用户交互
Web Worker (Partytown):
- GA / GTM / 等
- 通过 proxy 间接访问 DOM (synchronous via SharedArrayBuffer)
第三方 script 调 document.cookie / window.dataLayer → Partytown 代理
到 main thread → script 以为自己在 main thread 跑。
装 (Next.js)
npm install @builder.io/partytown
// next.config.js
module.exports = {
experimental: { nextScriptWorkers: true },
};
// _document.tsx 或 app/layout.tsx
import Script from 'next/script';
<Script
strategy="worker" // 关键
src="https://www.googletagmanager.com/gtag/js?id=G-XXX"
/>
<Script id="gtag-init" strategy="worker">
{`
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-XXX');
`}
</Script>
跑起来 → GA script 在 worker。
GA 看到的数据跟正常一样。
装 (vanilla / 任意 framework)
<head>
<script>
window.partytown = {
forward: ['dataLayer.push'], // 这些 method 转 worker
};
</script>
<script src="/~partytown/partytown.js"></script>
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXX"></script>
<script type="text/partytown">
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-XXX');
</script>
</head>
type="text/partytown" 是关键 → Partytown 拦截 + 在 worker 跑。
性能效果
我们一个 marketing 站:
| metric | before | after |
|---|---|---|
| Lighthouse Performance | 62 | 92 |
| TBT (Total Blocking Time) | 850ms | 80ms |
| TTI (Time to Interactive) | 5.2s | 2.1s |
| LCP | 3.8s | 2.5s |
主要 GA + GTM + HubSpot 三个第三方加起来约 600 KB JS。
丢 worker 后 main thread 几乎不卡。
副作用 / 限制
- 不能用
document.write:worker 没 DOM 写。多数现代 script
OK。 - synchronous DOM access 延迟:worker → main 调用有几 ms 开销。
GA 类 batched analytics 无感;动画类 script 不行。 - 某些 script 不兼容:检测自己环境会发现"不在 main thread"
报错。少数老 script。 - 需要
Cross-Origin-*header:用 SharedArrayBuffer 优化要 COOP/COEP。
不配也能跑(fallback 慢 IPC)。
哪些第三方适合
适合:
- analytics (GA, Mixpanel, Segment, Amplitude)
- tag manager (GTM)
- chat widget (Intercom, HubSpot)
- ad pixel (Facebook, TikTok)
- A/B testing (Optimizely)
不适合:
- 必须 sync DOM 操作(如 Stripe Elements 表单内嵌)
- 视频 / 动画 SDK(需 60fps)
- 关键功能 script(auth / payment 核心)
debug
Chrome devtools 看:
- main thread:你的 app
- worker thread:partytown + 第三方
network tab 看 partytown 跑的 request(带 partytown referer)。
与 GTM server-side 对比
GTM server-side container:把 GTM 处理移到自己 server,client 只发原始
event。
| partytown | GTM SS | |
|---|---|---|
| 复杂度 | 低 | 高 |
| client perf | 好 | 极好 |
| 成本 | 0 | server cost |
| 控制 | 中 | 强 |
大网站常两者结合:partytown for 简单 script + GTM SS for 核心 tracking。
与 facade pattern
YouTube embed / chat widget 等:先显示假封面,用户 hover/click 才加载真
iframe。
const [loaded, setLoaded] = useState(false);
return loaded ? <YouTubeEmbed /> : <FakePoster onClick={() => setLoaded(true)} />;
partytown 移走 main thread 加载;facade 直接延迟加载。
互补。
实际接入步骤
- Lighthouse 跑当前 → 找 main thread blocking script
- 列出第三方 script 清单
- 一个一个 wrap partytown → 测 GA / etc 仍正常报数据
- 重 Lighthouse → 看分提升
通常 1 天内能上线 + 显著效果。
静态站特别合适
Astro / Gatsby / 11ty 等静态站性能"就差第三方拖" → partytown 神药。
Astro 内置 partytown integration:
// astro.config.mjs
import partytown from '@astrojs/partytown';
export default { integrations: [partytown()] };
<script type="text/partytown" src="..."></script>
跟 Next.js Script 对比
Next.js 自己也有 Script strategy:
beforeInteractive:阻塞,head 内afterInteractive:default,body 末lazyOnload:idle 时worker:partytown(实验)
worker 内部就是 partytown 集成。
真实 case
某客户 marketing 站 SEO / 性能死活上不去:
- GTM 200 KB
- HubSpot 300 KB
- GA + 5 个 pixel
LCP 5s,Google 算"slow" → SEO 罚。
接 partytown 一周:
- LCP 2.3s
- Core Web Vitals 全绿
- 自然流量 +15%(SEO 改善)
第三方需求没变 → 性能彻底改善。
不要乱开
不是所有 script 都该丢 worker。
仔细想:这 script 真的不需要 sync main thread?
比如 Stripe element 在 form 里要 sync 渲染 → 不行。
GA fire-and-forget event → 完美。
踩过的坑
-
第三方更新挂掉:GA 新 SDK 跟 partytown 不兼容(罕见)。
monitor 数据连续性,发现异常 fallback 普通 script。 -
COOP/COEP header 跟 OAuth iframe 冲突:需要 cross-origin iframe
的功能可能挂。试个 staging。 -
dev mode performance 反慢:partytown 在 dev 调试模式 IPC 开
销大。生产无问题。 -
dataLayer race condition:用 partytown 后 dataLayer 是异步
推。需要 sync read 的 code 会拿空。 -
CSP 配 worker-src:CSP 严格时 worker 加载被 block。
worker-src 'self'加。
登录后参与评论。