React / Next.js アプリを速くするための実践テクニック集。Core Web Vitalsの改善に直結するものを優先度順に掲載。
React.memo は「重い」コンポーネントにだけ使う
React.memo は props の浅い比較でスキップするが、比較自体にコストがかかる。軽いコンポーネントに使うと逆にパフォーマンスが下がる。プロファイラで確認してから使う。
useCallback / useMemo の依存配列を正確に管理する
依存配列が不正確だとメモ化が無効になるか、古い値を参照し続けるバグになる。eslint-plugin-react-hooks の exhaustive-deps を有効にして自動検出する。
状態はできるだけ近い場所に置く(State Colocation)
状態をルートコンポーネントに集中させると、更新のたびに全ツリーが再レンダーされる。状態を使うコンポーネントの近くに置くことで再レンダーの範囲を最小化する。
大量リストは仮想化(Virtualization)する
1000件以上のリストを全部DOMに描画するとスクロールがカクつく。画面内に見えている部分だけを描画する「仮想化」でDOMノード数を大幅削減できる。
next/image で画像を自動最適化する
next/Image は WebP/AVIF への自動変換・遅延読み込み・サイズ最適化を自動で行う。LCP(Largest Contentful Paint)の改善に直結する。
next/font でフォントを最適化する
Googleフォントを next/font 経由で読み込むと、フォントファイルがセルフホストされ外部リクエストが不要になる。CLS(Cumulative Layout Shift)も防止される。
データ取得はServer Componentsで行う
Client ComponentのuseEffect+fetchはWaterfall問題(コンポーネントがマウントしてからfetchが始まる)が起きる。Server Componentならレンダー開始と同時にfetchが始まりTTFBが短縮される。
heavy コンポーネントは dynamic import で分割する
バンドルサイズを削減するため、重いコンポーネント(地図・エディタ・グラフなど)は dynamic() で遅延読み込みする。初期JSバンドルが小さくなりFCPが改善する。
LCP(最大コンテンツ描画)を 2.5 秒以内にする
ページ内の最も大きい要素が表示されるまでの時間。ヒーロー画像・大見出しが対象になることが多い。
CLS(レイアウトシフト)を 0.1 以下にする
コンテンツ表示後にレイアウトが崩れる度合い。画像のwidth/height未設定・動的挿入コンテンツ・フォント読み込みが主な原因。
INP(インタラクション応答速度)を 200ms 以内にする
ボタンクリックやキー入力への応答速度。メインスレッドをブロックする処理・大量の同期レンダリングが主な原因。
@next/bundle-analyzer でバンドルサイズを可視化する
バンドルサイズが大きい原因を特定するためのツール。どのパッケージが大きいかを視覚的に確認できる。