Partytown 완벽 가이드 — 서드파티 스크립트를 Web Worker로 옮겨 성능·INP까지 잡는 법

Partytown 완벽 가이드 — 서드파티 스크립트를 Web Worker로 옮겨 성능·INP까지 잡는 법

이 글의 핵심

Partytown은 서드파티 스크립트를 메인 스레드가 아닌 Web Worker에서 실행하도록 도와, 긴 작업과 이벤트 처리로 인한 INP·TBT 악화를 완화하는 도구입니다. DOM 접근은 프록시로 중재되며, GTM·GA는 forward 설정으로 dataLayer·gtag 호출을 메인으로 전달합니다.

들어가며

Partytown서드파티(third-party) 스크립트를 브라우저의 메인 스레드가 아니라 Web Worker에서 실행하도록 돕는 런타임·도구 모음입니다. 마케팅·분석·A/B 테스트·위젯 등으로 삽입되는 스크립트는 종종 메인 스레드에서 길게 실행되거나 이벤트 처리와 경쟁하여 INP(Interaction to Next Paint)·TBT(Total Blocking Time) 같은 지표를 악화시킵니다. Partytown은 이런 코드를 워커 쪽으로 격리해, 사용자 입력에 대한 반응성을 지키려는 목적에서 널리 논의됩니다.

이 글은 2026년 기준으로 Partytown의 아키텍처적 전제, 메인 스레드와의 통신, Google Analytics(GA)·Google Tag Manager(GTM) 연동, Next.js·Astro에서의 통합, 성능 측정·개선, 디버깅·트러블슈팅을 한 흐름으로 정리합니다. “설치만 하면 끝”이 아니라, 왜 동작하는지, 어디까지 기대할 수 있는지, 프로덕션에서 깨지는 지점을 구분하는 데 초점을 둡니다.


Partytown이 해결하려는 문제

메인 스레드 병목과 서드파티

브라우저에서 JavaScript는 기본적으로 메인 스레드 한 줄에서 HTML 파싱, 스타일·레이아웃, 페인트, 사용자 입력 처리, 페이지에 삽입된 서드파티 코드까지 함께 처리합니다. 서드파티 스크립트가 긴 작업(Long Task)을 만들면, 그동안 입력 지연이 누적됩니다. Core Web Vitals 관점에서는 INP가 특히 이런 경쟁과 연결됩니다.

전통적인 완화책은 지연 로드(첫 입력 이후 로드), 스크립트 개수·순서 조정, 리소스 힌트, 태그 정리입니다. Partytown은 여기에 더해 실행 위치 자체를 워커로 옮기는 접근입니다.

“옮긴다”의 의미

서드파티 코드는 원래 window·document에 직접 접근한다고 가정하고 작성되는 경우가 많습니다. Web Worker에는 DOM이 없으므로, Partytown은 워커 안에서 돌아가는 스크립트의 DOM·윈도우 접근을 가로채 메인 스레드에 위임하는 프록시(proxy) 계층을 둡니다. 즉 코드 물리적 위치는 워커, 화면·문서와의 상호작용은 메인이라는 이중 구조입니다.

이 구조 덕분에 CPU 짜임(스크립트 평가·내부 루프)의 상당 부분이 메인에서 벗어날 수 있지만, 대신 메인↔워커 메시지, 프록시 호출 비용이 생깁니다. 따라서 무조건 빨라진다기보다 특정 유형의 태그·사이트 구조에서 체감 이득이 큰 경우가 많다고 이해하는 것이 안전합니다.


핵심 개념

1) type="text/partytown"

일반 스크립트는 type을 생략하거나 text/javascript입니다. Partytown은 로드하되 메인에서 바로 실행하지 않고, 런타임이 워커로 가져가 실행할 대상으로 인식하도록 type="text/partytown"을 사용합니다. 빌드 도구·프레임워크 통합에서는 이 속성을 자동으로 붙이거나, 래퍼 컴포넌트가 주입합니다.

2) 런타임 라이브러리(~partytown)

Partytown은 페이지에 작은 부트스트랩 코드와 함께, 워커·메인 간 브리지를 구성하는 라이브러리 파일이 필요합니다. 통상 public/~partytown 같은 경로에 복사해 두고, 배포 시 정적 파일로 함께 올라가야 합니다. 이 경로가 빌드 산출물에 없으면 아무것도 실행되지 않거나 404로 끊깁니다.

3) forward — 메인으로 “그대로” 보낼 호출

분석·태그 도구는 dataLayer.push, gtag, 특정 전역 함수 호출로 이벤트를 쌓거나 후속 스크립트와 연동합니다. 워커 안에서 이 호출이 그대로 끝까지 워커에만 머무르면 GTM/GA가 기대하는 메인 스레드 쪽 동작과 어긋날 수 있습니다. forward 옵션은 이런 전역 함수 호출을 메인 스레드로 전달하도록 선언하는 역할을 합니다.

4) resolveUrl — 프록시와 CORS

일부 서드파티 스크립트는 절대 URL로 추가 리소스를 불러옵니다. 기업 프록시·CSP·광고 차단 환경에서는 자신의 오리진으로 리라이트하거나 역프록시가 필요할 수 있습니다. Partytown은 URL 해석을 바꿀 수 있는 훅을 제공하며, 이를 통해 동일 출처로 보이게 하거나 허용 도메인만 통과시키는 패턴이 쓰입니다.

5) 동기화 모델과 SharedArrayBuffer

DOM 접근 중 동기적으로 결과가 필요한 패턴(레거시 API 가정)이 있으면, 단순 postMessage만으로는 부족할 수 있습니다. Partytown 구현은 이런 구간에서 Atomics·SharedArrayBuffer 등을 활용하는 경로를 포함합니다. 이는 브라우저·헤더(COOP/COEP 등)·사이트 정책에 따라 사용 가능 여부가 달라질 수 있으므로, 스테이징·실제 사용자 환경에서의 검증이 중요합니다.


웹 워커 통신 메커니즘

메인 스레드와의 메시지 흐름

워커는 기본적으로 postMessage 기반 비동기 통신입니다. Partytown은 스크립트가 DOM 노드를 읽거나 쓰는 호출을 순차적으로 처리하기 위해 내부 프로토콜로 메인과 동기화합니다. 개발자가 이 프로토콜을 직접 다루지는 않지만, 통신량이 많은 스크립트프록시 오버헤드로 이득이 줄어들 수 있습니다.

프록시 비용이 커지는 경우

다음과 같은 패턴은 워커 이전 이점이 상쌓이거나 실패하기 쉽습니다.

  • document.write에 의존하는 동기 삽입
  • 동기 XMLHttpRequest
  • 메인 전용 API(일부 Clipboard, 파일 업로드 UI 등)에 대한 가정
  • 매 프레임 DOM을 세밀하게 읽고 쓰는 위젯

이럴 때는 Partytown 적용 전 태그 단위로 나누어 실험하거나, 해당 스크립트는 메인에 남기는 편이 낫습니다.

forward가 필요한 이유

GTM·GA4는 데이터 레이어와 전역 함수를 통해 후속 태그·동의 모듈·히트 전송을 연결합니다. 워커에서 실행되는 코드가 dataLayer.push를 호출하면, 그 호출이 메인의 dataLayer와 동일 객체를 바라보게 하려면 명시적 전달이 필요합니다. forward 배열에 dataLayer.push, gtag프로젝트에서 실제로 쓰는 전역 식별자를 넣는 것이 일반적입니다.


Google Analytics·GTM 통합

gtag.js(측정 ID 기반)

GA4를 gtag.js로 로드하는 경우, 스크립트 태그의 src는 보통 Google이 호스팅하는 gtag/js?id=... 형태입니다. Partytown을 쓸 때는 해당 <script>type="text/partytown"을 지정하고, forwarddataLayer.push, gtag(프로젝트에서 사용하는 이름에 맞게)를 포함합니다.

아래는 개념을 보여주는 예시이며, 실제 측정 ID·동의 모드·링크어트리뷰션 등은 사이트 정책에 맞게 조정해야 합니다.

<!-- 개념 예시: Partytown + gtag (실제 ID·동의 모드는 사이트에 맞게) -->
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){ dataLayer.push(arguments); }
</script>
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script type="text/partytown">
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

설명: 첫 블록에서 dataLayergtag 전역을 메인에 준비해 두면, 이후 text/partytown 스크립트는 워커에서 실행되지만, forward 설정을 통해 gtag·dataLayer.push 호출이 메인의 객체와 연결되도록 맞출 수 있습니다. 프레임워크 통합에서는 이 블록을 레이아웃 헤드에 두고, Partytown 컴포넌트에 forward를 선언합니다.

Google Tag Manager 컨테이너

GTM은 컨테이너 스니펫 하나로 다수의 태그를 로드합니다. Partytown으로 컨테이너 자체를 워커에 올리면, 내부 태그들도 워커 맥락에서 실행됩니다. 이는 의도한 시나리오(메인 부하 감소)일 수 있지만, 특정 커스텀 HTML 태그가 메인 DOM을 직접 조작한다고 가정하면 깨질 수 있습니다.

실무에서는 다음 순서를 권장합니다.

  1. GTM 미리보기로 태그·트리거 목록을 확정한다.
  2. 커스텀 스크립트·비표준 태그를 식별한다.
  3. Partytown ON 상태에서 스테이징에서 전환·이벤트·데이터 레이어를 검증한다.
  4. 문제 태그는 메인 로드로 분리하거나 태그 구현을 수정한다.

동의(Consent)·광고·링크 스팸 방지

동의 모드, 광고 태그, 연결된 도메인지역·정책·브라우저에 따라 다르게 동작합니다. Partytown은 실행 위치만 바꾸는 레이어이므로, CMP(동의 관리)와의 순서, dataLayer 초기화 시점, 스크립트 차단 확장 프로그램은 별도로 점검해야 합니다. “Partytown 적용 후 전환이 0”에 가까워지면, 워커에서의 dataLayer 동기화GTM 변수 스코프를 우선 의심합니다.


Next.js 통합

App Router(app/layout.tsx)

Next.js에서는 @builder.io/partytown/reactPartytown 컴포넌트를 루트 레이아웃의 <head>에 넣는 패턴이 흔합니다. forwarddataLayer.push 등을 넘기고, gtag 스크립트type="text/partytown"을 지정합니다.

// app/layout.tsx — 개념 예시 (경로·ID는 프로젝트에 맞게)
import { Partytown } from '@builder.io/partytown/react';
import Script from 'next/script';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <head>
        <Partytown forward={['dataLayer.push', 'gtag']} />
      </head>
      <body>
        {children}
        <Script id="gtag-base" strategy="beforeInteractive">
          {`
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
          `}
        </Script>
        <Script
          src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
          strategy="afterInteractive"
          type="text/partytown"
        />
        <Script id="gtag-config" type="text/partytown" strategy="afterInteractive">
          {`
            gtag('js', new Date());
            gtag('config', 'G-XXXXXXXXXX');
          `}
        </Script>
      </body>
    </html>
  );
}

설명: Partytown런타임 초기화를 담당합니다. next/scripttype="text/partytown"해당 리소스를 워커에서 실행하도록 표시합니다. beforeInteractive/afterInteractive는 Next의 로딩 전략이므로, 레이아웃 깜빡임·히드레이션 타이밍과 맞추어 조정합니다. 실제 프로젝트에서는 동의 모드 초기화가 먼저인지 gtag가 먼저인지를 법무·마케팅 요구에 맞게 재배치해야 합니다.

Pages Router·정적 복사

Pages Router를 쓰는 경우에도 _document.tsx 등에서 동일하게 Partytown를 넣을 수 있습니다. 중요한 것은 partytown copylibpublic/~partytown(또는 설정한 경로)에 라이브러리가 복사되고, 프로덕션 빌드 산출물에 포함된다는 점입니다. package.json빌드 전 복사 스크립트를 넣어 두면 실수를 줄일 수 있습니다.

{
  "scripts": {
    "postinstall": "partytown copylib public/~partytown"
  }
}

모노레포·서브패키지에서는 public 루트가 어디인지 한 번 더 확인합니다.


Astro 통합

Astro는 @astrojs/partytown 통합으로 설정을 단순화할 수 있습니다. astro.config.mjs에 통합을 추가하고, partytown 옵션에서 config.forward 등을 지정합니다.

// astro.config.mjs — 개념 예시
import { defineConfig } from 'astro/config';
import partytown from '@astrojs/partytown';

export default defineConfig({
  integrations: [
    partytown({
      config: {
        forward: ['dataLayer.push', 'gtag'],
      },
    }),
  ],
});

설명: 통합은 Partytown 스크립트 주입·정적 파일 처리를 프레임워크에 맞게 수행합니다. 페이지에서는 여전히 type="text/partytown"이 붙은 외부 스크립트를 선언해야 하며, 배포(Cloudflare Pages 등)에서 ~partytown 자원이 404가 아닌지 빌드 산출물로 확인합니다. Astro 버전별 옵션 이름은 해당 버전 문서를 따르는 것이 안전합니다.

---
// src/layouts/Layout.astro — 개념 예시
---
<!DOCTYPE html>
<html lang="ko">
  <head>
    <script is:inline>
      window.dataLayer = window.dataLayer || [];
      function gtag(){ dataLayer.push(arguments); }
    </script>
    <script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
    <script type="text/partytown" is:inline>
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXXXX');
    </script>
  </head>
  <body>
    <slot />
  </body>
</html>

성능 측정과 개선

측정 전후 프로토콜

Partytown 도입은 “체감이 빨라졌다”만으로 끝내기 어렵습니다. 아래를 동일 브랜치·동일 태그 구성에서 비교합니다.

  1. Lighthouse(랩): TBT, CLS, 상황에 따라 INP 관련 힌트
  2. Chrome DevTools Performance: Long Task 길이·빈도, 메인 스레드 유휴 구간
  3. 필드 데이터: PageSpeed Insights, RUM(가능 시), Search Console Core Web Vitals

랩과 필드가 다르게 나오는 것이 정상입니다. Partytown은 특히 메인 스레드 혼잡도가 높은 페이지에서 랩 지표 개선이 두드러질 수 있습니다.

기대할 수 있는 것·없는 것

  • 기대: 서드파티 평가·내부 작업이 메인과 격리되어 긴 작업 체인이 짧아지는 경우
  • 기대 어려움: 네트워크 자체가 병목(느린 서버, 거대한 스크립트 다운로드)이면 전송 시간은 그대로
  • 주의: 프록시·메시지 오버헤드로 오히려 느려질 수 있는 태그도 있음

Core Web Vitals와의 정렬

LCP는 주로 히어로 리소스·서버 응답·렌더 차단과 연결되고, Partytown이 직접 건드리는 비중은 사이트마다 다릅니다. INP·TBT메인 스레드 여유와 연관이 깊어 Partytown과 목표가 잘 맞는 편입니다. 그래도 실제 GTM 내부 태그가 메인에서 무엇을 하는지에 따라 결과는 달라집니다.


디버깅과 트러블슈팅

debug 모드

개발·스테이징에서 Partytown의 debug(또는 통합 옵션의 디버그 플래그)를 켜면 브리지·프록시 관련 로그를 더 자세히 볼 수 있습니다. 프로덕션에서는 로그 노이즈·정보 노출을 고려해 끄는 것이 일반적입니다.

자주 나오는 증상

증상가능 원인점검
분석 히트가 안 잡힘forward 누락, dataLayer 초기화 순서전역 함수·레이어가 메인에 있는지, forward 목록
~partytown 404copylib 누락, 잘못된 public 경로빌드 산출물에 파일 존재 여부
일부 태그만 실패해당 태그가 DOM·동기 API에 의존문제 태그를 메인으로 분리
로컬만 되고 배포만 실패헤더·CSP·경로 차이배포 URL에서 네트워크 탭·콘솔

CSP(콘텐츠 보안 정책)

script-src, worker-src, connect-src에 Partytown과 로드하는 서드파티 도메인이 포함되어야 합니다. 정책이 엄격한 프로젝트에서는 배포 직후에만 차단되는 경우가 많으므로, 스테이징에 프로덕션 CSP를 미러링하는 것이 좋습니다.

호환성 전략

“전부 Partytown”보다 “측정·태그는 워커, 위젯·레거시는 메인”처럼 경계를 나누는 설계가 운영 난이도를 낮춥니다. 신규 태그가 추가될 때마다 스테이징 검증을 자동화하면 회귀를 줄일 수 있습니다.


정리

Partytown은 서드파티 스크립트를 Web Worker에서 돌려 메인 스레드 부담을 줄이려는 도구입니다. 핵심은 text/partytown 타입, ~partytown 정적 자산, DOM 프록시·메인 브리지, GTM/GA를 위한 forward입니다. Next.js는 패키지 @builder.io/partytown/react로, Astro@astrojs/partytown 통합으로 설정을 단순화할 수 있습니다.

성능은 랩·필드를 함께 보고, 태그 단위 호환성을 검증해야 합니다. 디버깅에서는 debug, 404·CSP, forward·초기화 순서를 우선 확인하세요. 이 글의 코드는 개념 예시이며, 측정 ID·동의 모듈·기업 프록시는 반드시 귀하의 서비스 정책에 맞게 조정해야 합니다.


참고로 삼을 만한 방향

  • Partytown 공식 문서 및 저장소의 최신 옵션명·통합 방식
  • Google 태그 도움말의 동의 모드·GA4 설정
  • 사내 GTM 변경 관리(스테이징 워크플로)

배포 전에는 git add·git commit·git pushnpm run deploy 순서를 지키는 것이 이 저장소 관례입니다.