Tasuke HubLearn · Solve · Grow
#Web Performance

INP最適化決定版【2025年版】:反応性を根本から改善する実践テクニック

Interaction to Next Paint(INP)に特化した深掘りガイド。計測の組み立て、ボトルネックの見つけ方、長いタスク分割、Web Worker活用、React/Next.jsでの実装パターン、サードパーティ制御まで。すぐ使えるコード例付き。

時計のアイコン13 September, 2025
TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

この記事のゴール

INP(Interaction to Next Paint)の改善に“直接”効く施策だけを、実務順序でまとめました。PageSpeedのスコアを上げるためではなく、ユーザーの体感を確実に速くすることが目的です。


ベストマッチ

最短で課題解決する一冊

この記事の内容と高い親和性が確認できたベストマッチです。早めにチェックしておきましょう。

INPとは(FIDとの違い)

  • INP: ユーザーの操作(クリック、タップ、キー入力)から、視覚的更新が描画されるまでの最も遅い事例を評価(ページ滞在中の代表値)。目標は ≤ 200ms。
  • FID: 初回入力の遅延のみを評価(初回限定)。2024年にINPへ置換。INPはより実態の体験に近い指標です。

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

計測セットアップ(ラボ+フィールド)

まずは計測を仕込み、改善の前後で数値を比較できる状態にします。

npm i web-vitals
// app/web-vitals.ts: フィールド計測を送信
import { onINP, onLCP, onCLS } from 'web-vitals';

function send(metric: any) {
  navigator.sendBeacon?.('/api/metrics', JSON.stringify(metric))
    || fetch('/api/metrics', { method: 'POST', keepalive: true, body: JSON.stringify(metric) });
}

onINP(send);
onLCP(send);
onCLS(send);

DevToolsの Performance/Performance Insights では「Longest Interactions」を確認し、遅い操作のハンドラや再描画の原因(レイアウト/スタイル/スクリプト)を特定します。


さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

よくあるボトルネック分類

  • 長いタスク(≥ 50ms): ハンドラ内で重い計算、同期IO、巨大配列処理。
  • 過剰レンダリング: 状態設計の粗さにより不要な再レンダ。
  • レイアウトスラッシング: スタイル変更直後に offsetHeight などの同期レイアウト読み取り。
  • サードパーティスクリプト: イベント直後に同期ブロッキング実行。

長いタスクの検知は PerformanceObserver('longtask') が有効です。

// 長いタスクの検出(ローカル計測)
const po = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn('Long task', Math.round(entry.duration), 'ms', entry);
  }
});
po.observe({ type: 'longtask', buffered: true });

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

実践テクニック(効果の大きい順)

1) イベントハンドラの“即時完了主義”

  • ハンドラでは「最小のステート更新+軽い視覚反映」のみ行い、重処理は後回しに。
  • 同期ループ・巨大 map/filter/sort は禁止。必要なら分割 or ワーカーへ。
  • スクロール/タッチ系は passive: true でスレッドをブロックしない。
// passive リスナー例
window.addEventListener('touchstart', onTouch, { passive: true });
// React: 反応性を保ちつつ重処理を後回し
import { startTransition } from 'react';

function onClick() {
  // 即時の視覚更新(軽いstate)
  setOpen(true);
  // 重い処理は低優先度へ
  startTransition(() => computeSomethingBig());
}

2) 長いタスクを“割る”(chunking)

// 仕事を分割してメインスレッドを空ける
async function chunk<T>(items: T[], size = 200, fn: (x: T) => void) {
  for (let i = 0; i < items.length; i += size) {
    const slice = items.slice(i, i + size);
    for (const it of slice) fn(it);
    // 次のフレームまで譲る
    await new Promise(requestAnimationFrame);
  }
}
// 例: 巨大配列の前処理
await chunk(largeList, 500, (row) => preprocess(row));

Chromium 系では scheduler.postTask が使える環境もあります。利用可能な場合は低優先度で投入し、入力処理を阻害しないようにします(フォールバック必須)。

declare const scheduler: any;
const post = (cb: () => void) =>
  (scheduler?.postTask ? scheduler.postTask(cb, { priority: 'background' }) : setTimeout(cb, 0));

3) Web Worker へオフロード

UI スレッドをブロックする処理はワーカーに逃がします。

// worker.js
self.onmessage = (e) => {
  const result = heavyCompute(e.data);
  self.postMessage(result);
};
// main.ts
const worker = new Worker(new URL('./worker.js', import.meta.url));
worker.postMessage(payload);
worker.onmessage = (e) => setState(e.data); // 受け取り後にUI更新

4) React/Next.js の設計

  • サーバーコンポーネント優先で JS ペイロードを削減。
  • コンテキストの肥大化を避け、局所 useStatememo/useMemo で再レンダ境界を最小化。
  • 大きなリストは仮想化(react-virtualized 等)。
  • フォーム送信・データ更新は 楽観的UI を用い、重処理は startTransition かミューテーション後に非同期化。
// next/script でサードパーティを遅延
import Script from 'next/script';

<Script src="https://example.com/abtest.js" strategy="lazyOnload" />

5) レイアウト/スタイルの最適化

  • 変形は transform/opacity を優先、top/left のレイアウト誘発を避ける。
  • 連続アニメーション領域は will-change を限定的に使用。
  • コンテナ化: content-visibility: auto; contain-intrinsic-size: ...;
.card-list { content-visibility: auto; contain-intrinsic-size: 800px 600px; }

6) サードパーティの統制

  • 重要操作直後に実行される同期タグを排除。
  • 計測・広告は defer/lazyOnload、発火条件を厳格にして最小化。

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

デバッグの進め方(再現→特定→修正)

  1. DevTools Performance で遅い操作を記録、Longest Interactions を特定。
  2. 長いタスクの原因(スクリプト/レイアウト/スタイル)を突き止める。
  3. 上記テクニック(分割・ワーカー・優先度低下)を適用。
  4. 再計測して INP が ≤ 200ms を満たすか確認。

補助として、手動マークも有効です。

performance.mark('ui-start');
// UIを即時反映
setOpen(true);
// 重処理は後回し
startTransition(() => heavy());
performance.mark('ui-end');
performance.measure('ui-latency', 'ui-start', 'ui-end');

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

改善順テンプレ(チーム向け)

  1. 最遅の操作を1つ選び、再現用スクリプトを用意
  2. JSペイロード削減(使っていない依存の排除)
  3. ハンドラの即時完了化(最小更新+遅延実行)
  4. 長いタスクの分割 or ワーカー化
  5. 再レンダ境界の見直し(メモ化・リスト仮想化)
  6. サードパーティ遅延・条件分岐
  7. 再計測としきい値ゲート(CI)

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

まとめ

INPは「メインスレッドの占有時間」と「再描画までの経路」を短くすることで改善します。ハンドラの即時完了、長いタスクの分割、ワーカー化、そして JS ペイロード削減。この4点を軸に、計測と改善の反復で ≤ 200ms を安定達成しましょう。

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

この記事をシェア

続けて読みたい記事

編集部がピックアップした関連記事で学びを広げましょう。

#Web Performance

画像最適化A/B計測設計【2025年版】:LCP/INP/CLSとCVRで効果検証する

2025/9/13
#AI

【2025年最新】LLMファインチューニング効率化ガイド:コスト削減と精度向上を両立する実践テクニック

2025/11/27
#LLM

LLM推論コスト最適化パターン集【2025年版】:品質を落とさず費用を半減する

2025/9/13
#API

API レスポンス遅延完全解決ガイド【2025年実務パフォーマンス最適化決定版】

2025/8/17
#GraphQL

GraphQL N+1問題完全解決ガイド【2025年実務パフォーマンス最適化決定版】

2025/8/19
#データ

【2025年版】シンセティックデータガバナンス実践ガイド

2025/11/23