WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

【最新技術】Partytownで実現するWeb制作のパフォーマンス改善|サードパーティスクリプトの影響を最小化する方法

生徒

Webサイトが重くて、Lighthouseのスコアが上がらないんです。Google Analyticsなどのスクリプトが原因みたいで…

ペン博士

よーく聞くんだぞ!それならPartytownが解決してくれるぞい。サードパーティスクリプトをWeb Workerで実行して、メインスレッドへの影響を最小化する革新的な技術なんじゃ!

目次

この記事でわかること

  • Partytownの仕組みと、なぜサードパーティスクリプトがパフォーマンスを下げるのか
  • 3層アーキテクチャ(仮想DOM・同期XHR・Service Worker)の技術的な動作原理
  • Next.js・React・HTMLサイトへの具体的な導入手順とコード
  • GA4・GTM・Facebook Pixelの実装コード全文
  • 導入前後のLighthouseスコア比較と段階的導入のベストプラクティス

Partytownは、Builder.ioが開発したオープンソースライブラリで、サードパーティスクリプトをWeb Worker上で実行することでメインスレッドへの影響を最小化します。Total Blocking Time(TBT)を50〜90%削減し、Lighthouseスコアを大幅に改善できます。本記事では仕組み・導入手順・対応スクリプトを完全解説します。


なぜサードパーティスクリプトがパフォーマンスを下げるのか

Google Analytics・Google Tag Manager・Facebook Pixelなど、現代のWebサイトには多数の外部スクリプトが組み込まれています。これらはメインスレッドをブロックし、ユーザー体験を悪化させます。

問題1:メインスレッドのブロック

JavaScriptはシングルスレッドで動作するため、サードパーティスクリプトの実行中はユーザーの操作に応答できません。

<!-- 従来の問題:メインスレッドでの実行 -->
<script src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'GA_MEASUREMENT_ID');
</script>

<!-- この間、メインスレッドはブロックされる
     ユーザーのクリックやスクロールに反応できない -->

問題2:Total Blocking Time(TBT)の増加

Lighthouseで測定されるTotal Blocking Time(TBT)は、メインスレッドがブロックされている時間の合計です。典型的なサードパーティスクリプトの影響は以下の通りです。

スクリプトメインスレッドブロック時間
Google Analytics20〜50ms
Google Tag Manager50〜150ms
Facebook Pixel30〜80ms
広告タグ(複数)100〜300ms
合計200〜500ms以上

問題3:First Input Delay(FID)の悪化

FIDは、ユーザーが最初にページとインタラクション(クリックやタップ)してから、ブラウザが実際に応答するまでの時間です。サードパーティスクリプトがメインスレッドを占有していると、FIDが100ms以上になることもあります。


Partytownの仕組み|3層アーキテクチャ

Partytownは既存コードの書き換えなしでWeb Worker上での実行を実現するために、以下の3層構造で動作します。

レイヤー1:Web Worker内の仮想DOM環境

Web Worker内でDOMインターフェース(document・window・HTMLElementなど)を再実装し、仮想DOM環境を構築します。サードパーティスクリプトはこの仮想DOMを操作し、実際のDOMアクセスは内部で変換されます。

// Web Worker内で動作する仮想DOM
// document.body.clientWidth などのプロパティアクセスをキャッチ
const virtualDocument = {
  get body() {
    // プロパティアクセスをメインスレッドに問い合わせる
    return proxyToMainThread('document.body');
  }
};

// サードパーティスクリプトはこの仮想DOMを操作する
// 実際のDOMアクセスは内部で変換される

レイヤー2:同期的なXMLHttpRequest

DOMプロパティへのアクセスを同期的なXMLHttpRequestに変換します。これにより、非同期処理を同期的に見せかけることができます。

// プロパティアクセスをURL化
// 例:document.body.clientWidth
//   → /_partytown_/property?path=document.body.clientWidth

const xhr = new XMLHttpRequest();
xhr.open('GET', '/_partytown_/property?path=document.body.clientWidth', false); // false = 同期
xhr.send();

const value = JSON.parse(xhr.responseText);
return value; // 同期的に値を返す

レイヤー3:Service Workerによるインターセプト

Service WorkerがXMLHttpRequestをインターセプトし、メインスレッドと通信して実際のDOM値を取得します。

// Service Worker(sw.js)
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);

  // Partytownのリクエストを検出
  if (url.pathname.startsWith('/_partytown_/')) {
    event.respondWith(async () => {
      // メインスレッドと通信(postMessage)
      const result = await sendToMainThread({
        type: 'property-get',
        path: url.searchParams.get('path')
      });

      // 結果をレスポンスとして返す
      return new Response(JSON.stringify(result), {
        headers: { 'Content-Type': 'application/json' }
      });
    }());
  }
});

同期的DOMアクセスの実現フロー

  1. Web Worker:サードパーティスクリプトがdocument.body.clientWidthにアクセス
  2. 仮想DOM:アクセスをキャッチし、同期XHRリクエストを発行
  3. Service Worker:リクエストをインターセプトし、メインスレッドに問い合わせ
  4. メインスレッド:実際のdocument.body.clientWidthの値を返す
  5. Service Worker:値をレスポンスとして返す
  6. Web Worker:同期的に値を取得できる

サードパーティスクリプトからは通常のDOM操作と同じように見えるため、コードの書き換えが不要です。


Partytownの導入方法

Next.jsでの導入

# npmの場合
npm install @builder.io/partytown

# yarnの場合
yarn add @builder.io/partytown

# pnpmの場合
pnpm add @builder.io/partytown

next.config.jsの設定

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Partytownのファイルを静的ファイルとして提供
  async headers() {
    return [
      {
        source: '/~partytown/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

Partytownファイルのコピー

# publicディレクトリにPartytownファイルをコピー
npx partytown copylib public/~partytown

_document.tsxの設定

// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';

export default function Document() {
  return (
    <Html lang="ja">
      <Head>
        {/* Partytownの設定 */}
        <Script
          id="partytown-config"
          strategy="beforeInteractive"
          dangerouslySetInnerHTML={{
            __html: `
              partytown = {
                lib: "/~partytown/",
                forward: ["dataLayer.push", "gtag"]
              };
            `,
          }}
        />

        {/* Google Analytics(Partytownで実行) */}
        <Script
          src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
          type="text/partytown"
        />
        <Script
          id="google-analytics"
          type="text/partytown"
          dangerouslySetInnerHTML={{
            __html: `
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());
              gtag('config', 'GA_MEASUREMENT_ID');
            `,
          }}
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

設定のポイント:

  • type=”text/partytown”:このスクリプトをPartytownで実行する指定
  • forward:メインスレッドから呼び出せる関数を指定
  • lib:Partytownファイルの配置場所

Reactアプリでの導入

# インストール
npm install @builder.io/partytown

# publicディレクトリにファイルコピー
npx partytown copylib public/~partytown
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My React App</title>

  <!-- Partytownの設定 -->
  <script>
    partytown = {
      lib: "/~partytown/",
      forward: ["dataLayer.push", "gtag"]
    };
  </script>

  <!-- Google Analytics(Partytownで実行) -->
  <script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
  <script type="text/partytown">
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'GA_MEASUREMENT_ID');
  </script>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
</html>

HTMLサイトでの導入

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>My Website</title>

  <!-- Partytownのコアスクリプト -->
  <script>
    partytown = {
      lib: "/~partytown/",
      forward: ["dataLayer.push", "gtag"]
    };
  </script>
  <script src="/~partytown/partytown.js"></script>

  <!-- サードパーティスクリプト -->
  <script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
  <script type="text/partytown">
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'GA_MEASUREMENT_ID');
  </script>
</head>
<body>
  <h1>Welcome to My Website</h1>
</body>
</html>

対応するサードパーティスクリプト

Google Analytics 4(GA4)

<script>
  partytown = {
    lib: "/~partytown/",
    forward: ["dataLayer.push", "gtag"]
  };
</script>

<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script type="text/partytown">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>

Google Tag Manager(GTM)

<script>
  partytown = {
    lib: "/~partytown/",
    forward: ["dataLayer.push"]
  };
</script>

<script type="text/partytown">
  (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;j.type='text/partytown';
  f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXXXXX');
</script>

Facebook Pixel

<script>
  partytown = {
    lib: "/~partytown/",
    forward: ["fbq"]
  };
</script>

<script type="text/partytown">
  !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', 'YOUR_PIXEL_ID');
  fbq('track', 'PageView');
</script>

その他の対応スクリプト

  • Hotjar
  • Intercom
  • Segment
  • Mixpanel
  • Google AdSense
  • Twitter Pixel
  • LinkedIn Insight Tag

パフォーマンス改善の実測例

導入前後のLighthouseスコア比較

Lighthouse Performance Score: 65

First Contentful Paint (FCP): 1.2s
Largest Contentful Paint (LCP): 2.8s
Total Blocking Time (TBT): 450ms  ← サードパーティスクリプトが影響
Cumulative Layout Shift (CLS): 0.05
Speed Index: 2.5s

サードパーティスクリプトによるメインスレッドブロック:
- Google Analytics: 45ms
- Google Tag Manager: 120ms
- Facebook Pixel: 75ms
- Hotjar: 90ms
- 広告タグ: 120ms
合計: 450ms
Lighthouse Performance Score: 92

First Contentful Paint (FCP): 1.1s
Largest Contentful Paint (LCP): 2.4s
Total Blocking Time (TBT): 50ms  ← 90%削減!
Cumulative Layout Shift (CLS): 0.05
Speed Index: 2.2s

サードパーティスクリプトによるメインスレッドブロック:
- Partytown初期化: 30ms
- その他のオーバーヘッド: 20ms
合計: 50ms(450ms → 50ms、89%削減)
指標導入前導入後改善率
Performance Score6592+27pt
TBT450ms50ms89%削減
LCP2.8s2.4s14%改善
FID100ms以上10ms以下90%以上改善

Partytownのメリット・デメリット

項目内容
メリットメインスレッドの解放・既存コード書き換え不要・Core Web Vitals改善・ユーザー体験向上
デメリット・注意点通信オーバーヘッドあり・HTTPS環境必須・一部スクリプト非対応・ベータ段階のライブラリ

Core Web VitalsへのSEO効果

TBT・FID・LCPなどの指標が改善されることで、GoogleのSEOランキングにも好影響を与えます。Googleはページエクスペリエンス(Core Web Vitals)をランキング要因の一つとしています。

注意点の詳細

  • 通信オーバーヘッド:Web WorkerとメインスレッドのpostMessage通信にわずかなオーバーヘッドがある。ただしメインスレッドブロッキング削減効果の方が大きい
  • HTTPS環境必須:Service Workerの動作にはHTTPS環境が必要(localhostは例外)
  • 全スクリプトが動作するわけではない:iframe生成・document.writeなどを使う複雑なスクリプトは正しく動作しない可能性がある
  • 開発段階のライブラリ:活発に開発されているが本番導入は事前テストを徹底すること

導入時のベストプラクティス

1. 段階的な導入

<!-- フェーズ1:Google Analyticsのみ -->
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=GA_ID"></script>

<!-- フェーズ2:Google Tag Managerを追加 -->
<script type="text/partytown" src="https://www.googletagmanager.com/gtm.js?id=GTM_ID"></script>

<!-- フェーズ3:その他のスクリプトを追加 -->
<script type="text/partytown" src="..."></script>

2. 動作確認の徹底

  • ブラウザのコンソール:エラーが出ていないか確認
  • ネットワークタブ:スクリプトが読み込まれているか確認
  • Google Analytics:リアルタイムレポートでデータ送信を確認
  • Tag Assistant:Google Tag Managerのタグが発火しているか確認

3. forwardオプションの適切な設定

partytown = {
  lib: "/~partytown/",
  forward: [
    "dataLayer.push",      // Google Analytics/GTM
    "gtag",                // Google Analytics
    "fbq",                 // Facebook Pixel
    "hj",                  // Hotjar
    "_hsq.push"            // HubSpot
  ]
};

4. パフォーマンスの計測

# Lighthouse CLIでの計測
npx lighthouse https://example.com --view

# 重要な指標
# - Total Blocking Time(TBT)
# - First Input Delay(FID)
# - Performance Score

よくある質問

Partytownを使うとGoogle Analyticsの計測データに影響はありますか?

正しく設定した場合、計測データに影響はありません。forwardオプションにdataLayer.pushgtagを追加することで、メインスレッドから発火したイベントがWeb Worker上のGA4にも正しく渡されます。導入後は必ずGA4のリアルタイムレポートでデータ送信を確認してください。

Service Workerが対応していないブラウザではどうなりますか?

Service Worker非対応ブラウザ(旧式ブラウザなど)では、Partytownが自動的にフォールバック動作を行い、従来通りメインスレッドでスクリプトが実行されます。機能が失われることはありませんが、パフォーマンス改善の恩恵は受けられません。現代のブラウザはほぼすべてService Workerに対応しているため、実用上の問題はほとんどありません。

Astro・Nuxt・Gatsby でも使えますか?

使えます。AstroはPartytown公式インテグレーション(@astrojs/partytown)を提供しており、設定が非常に簡単です。NuxtはPartytownをNuxtモジュールとして利用できます。Gatsbyも公式プラグイン(gatsby-plugin-partytown)が存在します。各フレームワークの公式ドキュメントと合わせて確認してください。

WordPressでPartytownを使うことはできますか?

WordPressでの直接的な公式プラグインは現時点で存在しませんが、テーマのheader.phpやfunctions.phpにPartytownのスクリプトを手動で追加することで利用できます。または「Insert Headers and Footers」などのプラグインを使ってカスタムスクリプトを挿入する方法もあります。ただしWordPressはページキャッシュやCDNとの相性を事前に確認することを推奨します。

Partytownを使っても効果が出ない場合はどうすればよいですか?

まずLighthouse CLIでTBTの内訳を確認し、どのスクリプトが残りのブロッキングを起こしているかを特定します。Partytown非対応のスクリプト(document.writeを使うものなど)が含まれている場合は、それらを別の手段(遅延ロード・async/defer属性)で対処します。またforwardオプションに漏れがある場合もスクリプトが正しく動作しないため、コンソールエラーを確認してください。


まとめ

  • Partytownの仕組み:3層アーキテクチャ(仮想DOM・同期XHR・Service Worker)により、既存コードを書き換えずにサードパーティスクリプトをWeb Workerで実行できる
  • パフォーマンス改善:TBTを50〜90%削減、FIDを大幅に改善、Lighthouseスコアが大幅向上する
  • 既存コード不要:Google AnalyticsなどのスクリプトをそのままでPartytownに移行できる
  • 導入方法:Next.js・React・HTMLサイトのいずれでもtype="text/partytown"を追加するだけで導入できる
  • 対応スクリプト:GA4・GTM・Facebook Pixel・Hotjar・Segment・Mixpanelなど主要スクリプトが対応
  • 注意点:HTTPS必須・一部スクリプトは非対応・段階的な導入と動作確認の徹底が重要

Partytownは、サードパーティスクリプトによるパフォーマンス低下を解決する革新的な技術です。メインスレッドを解放することでユーザー体験が大幅に向上し、Core Web VitalsのスコアもGoogle検索のランキングに好影響を与えます。

WithCodeでは、最新のWeb制作技術を体系的に学べます。Partytownのような最先端の技術も含め、実務で即戦力となるスキルを身につけましょう。


WithCodeを体験できる初級コース公開中!

WithCode初級コース案内

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:ロードマップ/基礎知識/環境構築/HTML/CSS/JavaScript/パフォーマンス最適化/LP・ポートフォリオ作成
    → 最新のパフォーマンス改善技術を学ぶ「確かな成長」を実感できるカリキュラム

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCode(ウィズコード)は「目指すなら稼げる人材」をビジョンに、累計400名以上のフリーランスを輩出してきた超実践型プログラミングスクールです。150社以上の実案件支援を特徴にWeb制作・Webデザインなどの役立つ情報を現場のノウハウに基づいて発信していきます。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら
  • WithCareer
    - ウィズキャリ -

    就転職サポート

    大手エージェントのサポート下で
    キャリアアップを目指そう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次