dataLayerイベントのスケジューリングによるINPの最適化
GTMイベントをブラウザの描画後まで遅延させることで、INPスコアが改善する仕組み
TL;DR: dataLayer.push()の遅延実行によるINPの改善
イベントハンドラー内で dataLayer.push() が同期的に実行されると、ブラウザが視覚的なフィードバックを描画する前にGTMがタグを処理します。これによりmain threadがブロックされ、Interaction to Next Paint (INP)が悪化します。解決策は、requestAnimationFrame を使い、ブラウザが次のフレームを描画した後に dataLayer.push() を実行することです。このパターンにより、多くのサイトでINPが20〜100ms短縮され、不合格だったスコアが合格に改善することも珍しくありません。データ収集に50〜250msの遅延が生じますが、アクセス解析やマーケティングのトラッキングには何の影響もありません。
dataLayer.push()がINPを悪化させる仕組み
あるクライアントの案件で、ユーザーの操作後に dataLayer.push() が実行されるタイミングを再スケジュールしたところ、Interaction to Next Paint (INP)が100ms短縮されました。この解決策は、トラッキングよりもレンダリングを優先するJavaScriptのドロップインコードです。
最終レビュー: Arjen Karel(2026年3月)
Table of Contents!
同期的なdataLayer.push()の問題点
Google Tag Managerを扱っていれば、dataLayer.push() は誰もが知っているはずです。データレイヤーにイベントを送信する標準的な方法であり、ほとんどのサイト機能に深く組み込まれています。しかし問題は、それが実行されるタイミングを誰も疑問視していないことです。
イベントハンドラー(ボタンのクリック、フォームの送信、メニューの開閉など)の中で dataLayer.push() を直接呼び出すと、同期的に実行されます。そのイベントを発火条件としているGTMタグも即座に実行され、ブラウザがレイアウトを更新する前にmain threadをブロックしてしまいます。訪問者がボタンをクリックしても、GTMがバックグラウンドでトラッキングスクリプトを処理している間、画面には何も変化が起きません。この遅延こそがpresentation delayであり、INP悪化の最大の原因です。
以下のパフォーマンストレースは、大手ニュースサイトにおけるユーザー操作後のGTMの動作を示したものです。GTMタスクの実行に約90msかかり、全体のINPは263msまで膨らんでいます。この操作はCore Web Vitalsの基準を満たしていません。

解決策: まず描画、その後にpush
この解決策は、web.devのINP最適化ガイドに沿ったアプローチです。重要度の低いコードを実行する前に、ブラウザが描画できるようmain threadにyieldします。イベントハンドラー内でトラッキングを同期実行する代わりに、次の手順を踏みます。
- 視覚的な更新を行うコードを即座に実行します。
- ブラウザに描画させます。
- その後にdataLayerへpushします。
このパターンを適用した後の同じ操作が以下です。変更点は、ブラウザが次のフレームを描画した後に dataLayer.push() が実行されるようにしたことだけです。

先ほどは不合格だった操作が、今度は余裕で合格しています。データはすべて問題なくdataLayerへ届きます。異なるのは、GTMスクリプトがユーザーの操作に対するブラウザの応答前ではなく、応答した後に実行される点だけです。
awaitPaintヘルパー
このヘルパーは requestAnimationFrame を使い、ブラウザの描画後にコールバックを実行します。ネストされた setTimeout により、描画の直前ではなく、描画が確実に完了した後に処理が実行されます。これは、操作後の重要でないタスクを遅延させるために web.devが推奨しているパターン です。
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Fallback: RAFが実行されない場合(バックグラウンドタブなど)のハングを防ぐ
setTimeout(resolve, 200);
// 次のpaint後に実行
requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
if (typeof fn === 'function') {
fn();
}
}
ヘルパーの使い方
dataLayer.push() の呼び出しを awaitPaint でラップし、描画完了後まで実行を遅延させます。
function pushToDataLayer(event, data) {
window.dataLayer = window.dataLayer || [];
awaitPaint(() => {
window.dataLayer.push({
event,
...data,
timestamp: new Date().toISOString()
});
});
}
// 使い方
document.querySelector('.buy-button').addEventListener('click', () => {
// 視覚的なフィードバックを即座に実行
showLoadingSpinner();
// paint後にトラッキングを実行
pushToDataLayer('purchase_click', { productId: '123' });
});
手軽にテストするためのグローバルオーバーライド
すべての dataLayer.push() の呼び出しをリファクタリングすることなく、サイト全体でこのパターンをテストするには、push関数をグローバルにオーバーライドします。このスクリプトを、GTMコンテナスニペットの直後の <head> 内に配置してください。
<script type="module">
window.dataLayer = window.dataLayer || [];
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Fallback: RAFが実行されない場合(バックグラウンドタブなど)のハングを防ぐ
setTimeout(resolve, 200);
// 次のpaint後に実行
requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
if (typeof fn === 'function') {
fn();
}
}
if (window.dataLayer && typeof window.dataLayer.push === 'function') {
const originalPush = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function (...args) {
awaitPaint(() => {
originalPush(...args);
});
};
}
</script>
これにより、ページ上のすべての dataLayer.push() がオーバーライドされます。既存のGTMイベントトリガー、ハードコードされたトラッキングの呼び出し、プラグイン連携のすべてに、コード変更なしで自動的に効果が適用されます。
INPが改善する理由
INPは、ユーザーの操作からブラウザが視覚的な応答を描画するまでの時間を計測します。イベントハンドラー内でGTMのイベント処理を同期実行すると、main threadがブロックされ、レンダリングが妨げられます。2024 Web Almanacの調査では、中央値においてpresentation delayがINP悪化の最大の要因であり、トラッキングスクリプトがその主な原因の1つであることが判明しています。
数字がそれを裏付けています。2025 Web Almanacによると、モバイルページ全体では77%がINPに合格しているのに対し、アクセス数上位1,000サイトでは63%にとどまります。これらの人気サイトは、より多くのサードパーティ製スクリプトを読み込んでいるためです。イタリア最大のクラシファイド市場であるSubitoでは、GTM経由で読み込まれていた1つのTikTokトラッキングスクリプトを無効化しただけで、INPが208msから約170msへと短縮されました。わずか1つのスクリプトで、38msの改善です。
dataLayer.push() の実行を描画後まで遅延させることで、GTMの処理をユーザー操作のクリティカルパスから完全に排除できます。ユーザーには即座に視覚的なフィードバックが返ります。GTMも通常通り発火し、単に50〜250ms遅れるだけです。
固定の遅延、Idle Callback、scheduler.yield()を使わない理由
- 固定の
setTimeout(delay): ハードコードされた遅延(例:setTimeout(..., 100))は、レンダリング完了のタイミングを推測しているに過ぎません。遅延が長すぎればトラッキングが無駄に遅れ、短すぎれば描画をブロックしたままになります。 requestIdleCallback: ブラウザがアイドル状態のときに処理をスケジュールしますが、特定の操作の直後に実行される保証はありません。コールバックの実行が大幅に遅れる可能性や、ユーザーが離脱するまでに一切実行されない可能性もあります。scheduler.yield(): main threadにyieldするためのモダンなAPIです。タスクの優先度を維持できます(後続処理をキューの末尾に回すsetTimeoutとは異なります)。しかし、scheduler.yield()はコード実行前の描画完了を保証しません。今回のユースケースでは、レンダリングのライフサイクルに連動しているrequestAnimationFrameのほうが適しています。なお、scheduler.yield()は現時点でSafariに対応していません。
requestAnimationFrame + setTimeout の組み合わせは、まさにこのシナリオのために設計された手法です。requestAnimationFrame はブラウザが描画する直前に発火します。そしてネストされた setTimeout が、その描画完了後に実行される新しいタスクを生成します。この2つを組み合わせることで、遅延させたコードが視覚的な更新の後に実行されることが保証されます。これこそが、INPが計測している対象そのものです。
トレードオフ
- データ収集のわずかな遅延: 同期的なpushと比べて、イベントがGTMに届くのが50〜250ms遅れます。アクセス解析やマーケティングのトラッキングにおいて、実質的な影響はありません。
- 素早い離脱によるデータの欠損: 訪問者がイベントを発火させた後、遅延時間の枠内にページを離脱した場合、そのイベントが送信されない可能性があります。リダイレクトやページのunload直前に発生する重要なイベントには、
dataLayer.push()ではなくnavigator.sendBeacon()を使用してください。Beacon APIの詳細については、アナリティクススクリプトを制限すべき理由を参照してください。
大半のトラッキングにおいて、INP改善のメリットはわずかな遅延のデメリットをはるかに上回ります。イベントログにミリ秒単位の精度が求められるケース(リアルタイム入札や金融ダッシュボードなど)には適していませんが、それ以外のあらゆるケースで明らかに有効です。
オーバーライドを適用した後は、Real User Monitoringで改善効果を検証してください。lab dataでも違いを確認できますが、Core Web Vitalsのスコアを左右するのは、実際のネットワーク環境にいる実ユーザーから得られるfield dataです。
何が本当に遅いのか、見つけ出します。
フィールドデータでCritical Rendering Pathをマッピング。Lighthouseレポートではなく、優先順位付きの修正リストをお渡しします。
監査を依頼する