この記事のゴール
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 ペイロードを削減。
- コンテキストの肥大化を避け、局所
useStateとmemo/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、発火条件を厳格にして最小化。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
デバッグの進め方(再現→特定→修正)
- DevTools Performance で遅い操作を記録、Longest Interactions を特定。
- 長いタスクの原因(スクリプト/レイアウト/スタイル)を突き止める。
- 上記テクニック(分割・ワーカー・優先度低下)を適用。
- 再計測して INP が ≤ 200ms を満たすか確認。
補助として、手動マークも有効です。
performance.mark('ui-start');
// UIを即時反映
setOpen(true);
// 重処理は後回し
startTransition(() => heavy());
performance.mark('ui-end');
performance.measure('ui-latency', 'ui-start', 'ui-end');さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
改善順テンプレ(チーム向け)
- 最遅の操作を1つ選び、再現用スクリプトを用意
- JSペイロード削減(使っていない依存の排除)
- ハンドラの即時完了化(最小更新+遅延実行)
- 長いタスクの分割 or ワーカー化
- 再レンダ境界の見直し(メモ化・リスト仮想化)
- サードパーティ遅延・条件分岐
- 再計測としきい値ゲート(CI)
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。
まとめ
INPは「メインスレッドの占有時間」と「再描画までの経路」を短くすることで改善します。ハンドラの即時完了、長いタスクの分割、ワーカー化、そして JS ペイロード削減。この4点を軸に、計測と改善の反復で ≤ 200ms を安定達成しましょう。
さらに理解を深める参考書
関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。




