Back

Interaction to Next Paint (INP): 究極の最適化ガイド (2025)

2024年3月、GoogleはFirst Input Delay (FID) を Interaction to Next Paint (INP) に正式に置き換え、Core Web Vitalとしました。これは単なる指標の変更ではなく、Webの応答性を測定する方法における根本的なシフトです。

長年、開発者は「最初の」クリックの最適化に注力してきました。しかし、ユーザーは一度だけクリックするわけではありません。入力し、切り替え、ドラッグし、そのたびにインターフェースが 即座に 反応することを期待します。FIDが第一印象だけを測定していたのに対し、INPは 全体的な関係 を測定します。

もし「Total Blocking Time」(TBT) が高い場合、あるいはLighthouseのスコアは緑色なのに実際のユーザーから「UIが重い」というフィードバックを受けているなら、この記事はあなたのためのものです。ブラウザのメインスレッドの仕組みを深く掘り下げ、なぜインタラクションが遅延するのか、そしてそれをどう修正すべきかを解説します。

インタラクションの解剖学 (The Anatomy of an Interaction)

INPを最適化するには、ユーザーがボタンをクリックしたときに何が起こるかを理解する必要があります。それは瞬時ではありません。ブラウザは3つの異なるフェーズを経ます。

  1. 入力遅延 (Input Delay): ユーザーがタップしてから、ブラウザがイベントハンドラの実行を 開始 するまでの時間です。多くの場合、メインスレッドが他のタスク(例:ユーザーがクリックした瞬間に走るハイドレーション処理など)でブロックされていることが原因です。
  2. 処理時間 (Processing Time): イベントコールバック(Reactの状態更新、DOM操作、複雑なロジックなど)を実行するのにかかる時間です。
  3. 表示遅延 (Presentation Delay): コードの実行が終了してから、ブラウザが実際に次のフレームを画面に描画するまでの時間です。

INP = 入力遅延 + 処理時間 + 表示遅延

ほとんどの開発者はステップ2(コードの最適化)にのみ焦点を当てます。しかし、悪いINPスコアの大部分はステップ1と3から発生します。

なぜ console.time() は嘘をつくのか

クリックハンドラを console.time() で囲んで測定すると、20msで実行されているとします。「よし、コードは速いぞ」と思うでしょう。しかし、INPスコアは400msを報告します。なぜでしょうか?

それは、console.time() がJavaScriptの実行時間しか測定しないからです。レンダリングコスト は測定しません。

もし20msのReact状態更新がコンポーネントツリー全体の再レンダリングを引き起こし、5,000個のDOMノードのスタイルとレイアウトを再計算させているとしたら? その「レイアウトスラッシング (Layout Thrashing)」は、JavaScriptが終了した 、次のペイントの に発生します。これが 表示遅延 (Presentation Delay) であり、INPスコアを悪化させる主犯です。

戦略1: メインスレッドを譲る (Yielding to the Main Thread)

応答性の黄金律は、「メインスレッドを独占しない」 ことです。

長時間実行されるタスク(例:大量のデータ配列の処理)があると、ブラウザは「フリーズ」します。新しい入力を処理したり、画面を描画したりすることができません。解決策は、長いタスクを小さなチャンクに分割し、その合間にブラウザに制御を「譲る (yield)」ことです。

従来の方法: setTimeout

function processData(items) { if (items.length === 0) return; // アイテムを1つ処理 doHeavyWork(items[0]); // 残りを後でスケジュール setTimeout(() => { processData(items.slice(1)); }, 0); }

これは機能しますが、setTimeout には最小遅延(多くの場合4ms以上)があり、タスクの優先順位を賢く管理できません。

現代的な方法: scheduler.yield()

新しい scheduler.yield() API(現在オリジントライアル中またはポリフィルで使用可能)を使用すると、setTimeout のペナルティなしにメインスレッドに譲り、即座に実行を再開できます。

async function processData(items) { for (const item of items) { doHeavyWork(item); // メインスレッドに譲り、ブラウザに入力/描画を処理させる // これは保留中のユーザー入力があるかどうかをチェックします! await scheduler.yield(); } }

これはゲームチェンジャーです。ブラウザにこう伝えているようなものです。「一瞬止まるよ。待っているクリックはある? あれば今処理して。なければ続けるよ。」

戦略2: 表示遅延 (Presentation Delay) の最適化

JavaScriptは速いのにペイントが遅い場合、レンダリングの問題があります。

  1. DOMサイズを減らす: 巨大なDOMツリーは、スタイル再計算 (Style Recalculation) とレイアウト (Layout) のコストを増加させます。長いリストは仮想化 (Virtualization) しましょう。
  2. CSS Containment: content-visibility: auto プロパティを使用して、画面外のコンテンツのレンダリングをスキップしましょう。
  3. レイアウトスラッシングを避ける: レイアウトプロパティ(offsetHeight など)を書き込んだ直後に読み取らないでください。これはブラウザに同期的なレイアウト計算を強制します。

戦略3: 即時フィードバック (Optimistic UI)

心理的に、ユーザーは反応がないとアプリが「遅い」と感じます。技術的には、INPは 次のペイント までの時間を測定します。

重い操作(API呼び出しなど)がある場合は、即座に何かを描画してください。

悪いパターン:

button.addEventListener('click', async () => { const data = await fetchData(); // ネットワーク待ち... renderData(data); // ...その後ペイント });

INPにネットワーク待機時間が含まれてしまいます!

良いパターン:

button.addEventListener('click', () => { showSpinner(); // 即座にペイント! INPの測定はここで終了。 fetchData().then(data => { renderData(data); hideSpinner(); }); });

スピナーやアクティブ状態 (Active State) を即座に表示することで、ブラウザの視点からインタラクションを「完了」させます。その後のネットワークリクエストと最終的なレンダリングは別のタスクであり、INPスコアを傷つけません。

結論

INPは厳しい指標ですが、私たちにより良いソフトウェアを構築することを強制します。「JavaScript偏重、メインスレッド遮断」のアーキテクチャから、「非同期的で、譲り合い、応答性の高い」設計へと私たちを導きます。

最も遅いインタラクションのプロファイリングから始めましょう。ロジックの問題ですか? レンダリングの問題ですか? それとも単にメインスレッドが忙しいだけですか? ボトルネックを特定したら、scheduler.yield()、DOM仮想化、楽観的UI更新などのツールがあなたを待っています。

あなたのサイトを「即座」に感じられるものにしましょう。ユーザー(そしてSEO順位)はあなたに感謝するでしょう。

Web PerformanceCore Web VitalsJavaScriptOptimizationINP

関連ツールを見る

Pockitの無料開発者ツールを試してみましょう