
はじめに
サイトの体感速度を数値で把握したいとき、多くのチームがCore Web Vitals(LCP・INP・CLS・FCP・TTFB)の計測に行き着きます。ただし「計測して終わり」ではなく、既存のアクセス解析基盤(GTM/dataLayerなど)に流し込んで初めて、他の指標と突き合わせた分析ができるようになります。
このとき地味に難しいのが、計測の仕組みそのものより「どう安全に、どう壊れにくく組み込むか」という周辺設計です。今回は、あるプロジェクトでCore Web Vitalsの計測結果をdataLayerへ送信する仕組みを実装した際の判断を、汎用化して整理します。
こんな人におすすめ
- パフォーマンス計測をGTMなどの既存アクセス解析基盤に統合したい方
- Cookie同意管理と計測イベントの両立に悩んでいる方
- サードパーティのJSライブラリをnpm依存にするか迷っている方
- 実測値に依存するテストを安定させたい方
- CIとローカルのテスト実行環境がなぜかズレる、という経験がある方
サードパーティ計測ライブラリをベンダリングするという選択
計測ライブラリをnpmパッケージとして依存関係に入れ、バンドラーを通して配信するのが一般的です。ただし、ビルドパイプラインに手を入れたくない事情がある場合、公式配布のビルド済みファイルをそのままvendorディレクトリに置いてしまう、という選択肢もあります。
assets/js/vendor/vendor-lib.iife.js
このファイルの先頭に、取得元URL・ライセンス・更新手順をコメントで明記しておくと、後から見た人が「どこから来たコードか」「どう更新すればいいか」を追えます。
/*!
* vendor-lib (vendored)
* Source: <公式配布元のURL>
* License: <ライセンス種別>
* Update: `npm pack` で最新版を取得し、dist配下のファイルで本体を差し替える
*/
あわせて、.gitattributesにlinguist-vendoredを指定し、フォーマッタの除外設定(.prettierignoreなど)にも同じパスを追記しておくと、言語統計やdiffレビューのノイズにならずに済みます。ここは片方だけ設定すると「統計には出ないがフォーマッタは触ろうとする」というズレが起きやすいポイントです。
同意ゲートは「その場で判定」、送るフィールドは「使う分だけ」
計測イベントを送信する際は、既存のCookie同意状態を確認してから送るのが大前提です。ここで選択肢になるのが、「同意が得られるまでイベントをバッファリングして、後からまとめて送る」か、「その場で同意状態を判定し、拒否中のイベントは捨てる」かという設計です。
今回は後者を選びました。同意タイミングとイベントの対応関係を曖昧にしないための判断です。
function isConsentGranted() {
return (
typeof window.AnalyticsClient !== 'undefined' &&
window.AnalyticsClient.getConsentState() === 'granted'
);
}
function sendToDataLayer(metric) {
if (!isConsentGranted()) {
return;
}
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'web_vitals',
metric_name: metric.name,
metric_rating: metric.rating,
metric_value: metric.value,
metric_delta: metric.delta,
navigation_type: metric.navigationType,
page_path: normalizePagePath(),
});
}
送信フィールドも、計測ライブラリが返すオブジェクトをそのまま渡すのではなく、必要なものだけを列挙しています。ライブラリの返すid(セッション追跡に使えてしまう可能性がある一意ID)やattribution(DOM要素の詳細情報)、entries(Performance Entryの生データ)は送らず、集計に必要なmetric_name/metric_rating/metric_value/metric_delta/navigation_type/page_pathのみを送ります。「送れるものを全部送る」ではなく「使う分だけ送る」という方針です。
また、既存の許可リスト機構が別のイベント種別向けに作られていてデータ形状が異なる場合、無理に共通化せず、新しいイベント専用の許可リストをその場で組み立てる方が事故が少ない、という判断もありました。形が違うものを無理に揃えると、どちらの用途にも中途半端な共通コードになりがちです。
依存関係を明示したdefer読み込み
計測ライブラリ本体と、それを使うクライアントスクリプトは別ハンドルとして登録し、依存配列で読み込み順序を保証します。WordPress環境を想定した例です。
wp_enqueue_script(
'vendor-lib',
ASSETS_PATH . 'js/vendor/vendor-lib.iife.js',
array(),
plugin_version,
array( 'strategy' => 'defer' )
);
wp_enqueue_script(
'metrics-client',
ASSETS_PATH . 'js/metrics-client.js',
array( 'vendor-lib', 'analytics-core' ),
plugin_version,
array( 'strategy' => 'defer' )
);
両方ともdeferで読み込みつつ、依存配列に「ライブラリ本体」と「同意管理オブジェクト」を明示することで、非同期読み込みでも実行順序が壊れないようにしています。初回描画をブロックしないことと、実行順序を保証することを両立させる、よくある定石です。
計測ライブラリをスタブに差し替えてテストを決定的にする
LCPやCLSといった実測値は、ブラウザ環境やタイミングに依存して不安定になりがちです。そこで、計測ライブラリのコールバック部分だけをスタブに差し替え、「イベントが発火したときに何を送るか」というロジックだけを決定的にテストします。Playwrightでの例です。
await page.evaluate(() => {
(window as any).__metricCallbacks = {};
(window as any).vendorLib = {
onMetricA: (cb) => ((window as any).__metricCallbacks.A = cb),
onMetricB: (cb) => ((window as any).__metricCallbacks.B = cb),
};
});
await page.addScriptTag({ path: clientScriptPath });
// テスト側から任意のタイミングでコールバックを直接呼び、送信内容を検証する
await page.evaluate((m) => window.__metricCallbacks.A(m), fakeMetric());
実際のパフォーマンス計測を待つのではなく、コールバックを直接叩いて送信内容を検証する形にすることで、テストの実行時間も安定性も確保しやすくなります。
つまづきやすいポイント
.gitattributesのlinguist-vendoredだけ設定してフォーマッタの除外設定を忘れると、ベンダリングしたファイルにフォーマッタが手を入れてしまうことがあります- 同意ゲートをその場で判定する設計は、後からバッファリングを追加したくなる誘惑がありますが、状態管理が複雑になりやすいため慎重な判断が必要です
- 既存の許可リストと形が違うデータを無理に共通化しようとすると、どちらの用途にも中途半端なコードになりがちです
- ローカルのpre-pushフックが使うテストコマンドとCIの実行コマンドが微妙に異なっていると、カバレッジ収集の方式の違いなどが原因でローカル環境限定の異常終了につながることがあります。「CIで通っているから大丈夫」と考えず、両者のコマンドを揃えておくと安心です
まとめ
パフォーマンス計測をアクセス解析基盤に統合する作業は、計測ロジックそのものより、配布方法・プライバシー配慮・読み込み順序・テストの安定性といった周辺設計の積み重ねで品質が決まります。特に、同意状態をその場で判定する設計や、送信フィールドを絞り込む方針は、後から機能を追加する際にも判断基準として役立ちます。ローカル環境とCI環境のコマンドを揃えておくことも、地味ですが再現性のあるトラブルシューティングにつながる観察でした。

