INPを最適化するためのdataLayerイベントのスケジューリング
ブラウザの描画後までGTMイベントを遅延させることでINPスコアを改善する方法

TL;DR: dataLayer.push()を遅延させてINPを改善する
イベントハンドラ内でdataLayer.push()が同期的に発火すると、ブラウザが視覚的なフィードバックを描画する前にGTMがタグを処理します。これによりメインスレッドがブロックされ、Interaction to Next Paint (INP)が悪化します。解決策:requestAnimationFrameを使用して、ブラウザが次のフレームを描画した後にdataLayer.push()を実行するようにスケジュールします。このパターンにより、ほとんどのサイトでINPが20〜100ミリ秒短縮され、不合格のスコアが合格に変わることもよくあります。データ収集は50〜250ミリ秒遅れますが、アナリティクスやマーケティングトラッキングには影響しません。
dataLayer.push()がINPに悪影響を与える理由
あるクライアントのケースでは、ユーザー操作後のdataLayer.push()の実行タイミングを再スケジュールすることで、Interaction to Next Paint (INP)が100ミリ秒短縮されたことを確認しました。この解決策は、トラッキングよりもレンダリングを優先する、導入が簡単なJavaScriptの代替手段です。
最終レビュー:Arjen Karel (2026年3月)
Table of Contents!
同期的なdataLayer.push()の問題点
Google Tag Managerを使用している場合、dataLayer.push()をご存知でしょう。これはイベントをData Layerに送信するための標準的な方法であり、ほとんどのサイト機能に深く統合されています。問題は、それがいつ実行されるのか誰も疑問に思わないことです。
イベントハンドラ(ボタンのクリック、フォームの送信、メニューの切り替え)内でdataLayer.push()が直接呼び出されると、同期的に実行されます。そのイベントで発火するように設定されたGTMタグも直ちに実行され、ブラウザがレイアウトを更新する前にメインスレッドをブロックします。訪問者がボタンをクリックしても、バックグラウンドでGTMがトラッキングスクリプトを処理している間は何も起こりません。この遅延がpresentation delayであり、INP悪化の主な原因です。
以下の主要なニュースサイトのパフォーマンスのトレースは、ユーザー操作後のGTMのアクティビティを示しています。GTMタスクの実行に約90ミリ秒かかり、全体のINPが263ミリ秒に押し上げられました。このインタラクションはCore Web Vitalsで不合格となります。

解決策:先に描画し、後からプッシュする
この解決策は、web.devのINP最適化ガイダンスに沿っています。非クリティカルなコードを実行する前にブラウザが描画できるように、メインスレッドにyieldするのです。イベントハンドラ内でトラッキングを同期的に実行するのではなく、次のようにします。
- 視覚的な更新のためのコードを直ちに実行する。
- ブラウザに描画させる。
- その後、dataLayerにプッシュする。
このパターンを適用した後の同じインタラクションは以下の通りです。唯一の変更点は、dataLayer.push()がブラウザが次のフレームを描画した後に実行されるようになったことです。

以前は不合格だったインタラクションが、今では余裕で合格しています。すべてのデータは依然としてdataLayerに到達します。違いは、GTMスクリプトがユーザーの操作に対するブラウザの応答の「前」ではなく、「後」に実行されるということです。
awaitPaintヘルパー
このヘルパーは、requestAnimationFrameを使用してブラウザの描画後にコールバックをスケジュールします。ネストされたsetTimeoutにより、関数が描画の直前ではなく、描画の完了後に実行されることが保証されます。これは、インタラクション後に非クリティカルな作業を遅延させるための、web.devが推奨するパターンです。
async function awaitPaint(fn) {
await new Promise((resolve) => {
// Fallback: ensures we don't hang if RAF never fires (e.g. background tabs)
setTimeout(resolve, 200);
// Schedule after the next 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()
});
});
}
// Usage
document.querySelector('.buy-button').addEventListener('click', () => {
// Visual feedback happens immediately
showLoadingSpinner();
// Tracking fires after paint
pushToDataLayer('purchase_click', { productId: '123' });
});
簡単なテストのためのグローバルなオーバーライド
すべてのdataLayer.push()呼び出しをリファクタリングせずにサイト全体でこのパターンをテストするには、push関数をグローバルにオーバーライドすることができます。このスクリプトを、GTMコンテナスニペットの直後の<head>内に配置します。
<script type="module">
window.dataLayer = window.dataLayer || [];
async function awaitPaint(fn) {
return new Promise((resolve) => {
const fallbackTimeout = setTimeout(() => {
if (typeof fn === 'function') { fn(); }
resolve();
}, 200);
requestAnimationFrame(() => {
setTimeout(() => {
clearTimeout(fallbackTimeout);
if (typeof fn === 'function') { fn(); }
resolve();
}, 50);
});
});
}
if (window.dataLayer && typeof window.dataLayer.push === 'function') {
const originalPush = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function (...args) {
(async () => {
await awaitPaint(() => {
originalPush(...args);
});
})();
};
}
</script>
これにより、ページ上のすべてのdataLayer.push()がオーバーライドされます。既存のすべてのGTMイベントトリガー、ハードコーディングされたトラッキング呼び出し、およびプラグインの統合は、コードを変更することなく自動的にその恩恵を受けます。
これがINPを改善する理由
INPは、ユーザーの操作からブラウザが視覚的な応答を描画するまでの時間を測定します。イベントハンドラ内で同期的にGTMのイベント処理を実行すると、メインスレッドがブロックされ、レンダリングが妨げられます。2024 Web Almanacでは、中央値においてpresentation delayがINP悪化の最大の要因であり、トラッキングスクリプトがその主な原因の1つであることが判明しました。
数字もこれを裏付けています。2025 Web Almanacによると、モバイルページの77%が全体としてINPで合格していますが、アクセス数の多い上位1,000サイトではわずか63%にとどまっています。これらのトップサイトは、より多くのサードパーティスクリプトをロードしています。Subito(イタリア最大のクラシファイドマーケットプレイス)では、GTM経由で読み込まれる単一のTikTokトラッキングスクリプトを無効にすることで、INPが208ミリ秒から約170ミリ秒に低下しました。1つのスクリプトで38ミリ秒の短縮です。
dataLayer.push()を描画後まで遅延させることで、すべてのGTM処理をインタラクションのクリティカルパスから外すことができます。ユーザーは直ちに視覚的なフィードバックを得られます。GTMは依然として発火しますが、単に50〜250ミリ秒遅くなるだけです。
固定の遅延、Idle Callback、またはscheduler.yield()を使用しない理由
- 固定の
setTimeout(delay):ハードコーディングされた遅延(例:setTimeout(..., 100))は、レンダリングがいつ完了するかを推測するものです。長すぎると不必要にトラッキングが遅れ、短すぎると依然として描画をブロックします。 requestIdleCallback:ブラウザがアイドル状態のときに作業をスケジュールしますが、特定のインタラクションの直後に実行されるという保証はありません。コールバックの実行がずっと後になったり、ユーザーが別のページに移動する前に全く実行されなかったりする可能性があります。scheduler.yield():メインスレッドにyieldするためのモダンなAPIです。(後続のタスクをキューの最後に送るsetTimeoutとは異なり)タスクの優先度を維持します。しかし、scheduler.yield()は、あなたのコードが実行される前に描画が行われたことを保証するものではありません。今回の特定のユースケースでは、requestAnimationFrameの方がレンダリングのライフサイクルと結びついているため、より優れたツールとなります。なお、scheduler.yield()はSafariではまだサポートされていません。
requestAnimationFrame + setTimeoutの組み合わせは、このシナリオのために特別に設計されています。requestAnimationFrameはブラウザが描画する直前に発火します。ネストされたsetTimeoutは、その描画が完了した後に実行される新しいタスクを作成します。これらが一緒になって、遅延させたコードが視覚的な更新の「後」に実行されることを保証します。これこそがINPの測定対象なのです。
トレードオフ
- データ収集における微小な遅延:イベントは、同期的なプッシュに比べて50〜250ミリ秒遅れてGTMに到達します。アナリティクスやマーケティングトラッキングにおいて、これは影響がありません。
- 即座の離脱によるデータ損失:訪問者がイベントをトリガーし、遅延時間の枠内にページを離れた場合、そのイベントは発火しない可能性があります。リダイレクトやページのアンロード前の重要なイベントについては、
dataLayer.push()の代わりにnavigator.sendBeacon()を使用してください。Beacon APIの詳細については、アナリティクススクリプトを制限する理由を参照してください。
ほとんどのトラッキングにおいて、INPの向上はわずかな遅延をはるかに上回るメリットをもたらします。イベントロギングにおいてミリ秒単位の精度が必要な場合(リアルタイム入札、財務ダッシュボードなど)には、このアプローチは適していません。それ以外のすべての場合において、これは明確な勝利です。
オーバーライドを適用した後は、Real User Monitoringで改善を確認してください。ラボテストでも違いは示されますが、Core Web Vitalsスコアで重要になるのは、実際のネットワーク上の実際のユーザーから得られたフィールドデータです。

