「速い」は感想ではなく測れる品質です。本記事は Next.js を前提に、推測ではなく数値で改善する手順を示します。
1. 3指標の正体と合格ライン
| 指標 | 何を測るか | 良好 | 要改善 | 不良 |
|---|---|---|---|---|
| LCP(Largest Contentful Paint) | 主要コンテンツの表示速度 | 2.5秒未満 | 2.5〜4.0秒 | 4.0秒超 |
| INP(Interaction to Next Paint) | 操作への応答性 | 200ms未満 | 200〜500ms | 500ms超 |
| CLS(Cumulative Layout Shift) | レイアウトのズレ | 0.1未満 | 0.1〜0.25 | 0.25超 |
重要な前提が2つあります。
- 75パーセンタイルで判定。 「訪問の75%が良好」でなければ合格しません。平均値が良くても、低速端末・低速回線のユーザーが足を引っ張ると落ちます。
- フィールドデータが正。 Google が見るのは実ユーザー(CrUX)です。手元の Lighthouse スコアが100でも、現場が遅ければ不合格になります。
2. INP を潰す(2026年・最重要)
INP は「クリック・タップ・キー入力をしてから、画面が次に描画されるまで」の遅延を測ります。原因はほぼ常にメインスレッドを塞ぐ JavaScriptです。
2-1. そもそも JS を送らない(最大のレバー)
React Server Components(RSC)は、サーバーで完結する処理のJSをクライアントへ送りません。"use client" を最小の葉に閉じ込めることが INP 改善の本丸です。
// ❌ ページ全体をクライアント化:不要な JS が大量にハイドレーションされる
"use client";
export default function Page() { /* 重い */ }
// ✅ 静的部分は RSC のまま。対話する葉だけ client にする
export default function Page() {
return (
<>
<ProductInfo /> {/* RSC:JS ゼロ */}
<AddToCartButton /> {/* "use client" の小さな葉だけ */}
</>
);
}
2-2. 長いタスクを分割し、メインスレッドに譲る
重い更新は useTransition / useDeferredValue で「緊急でない」と印を付け、入力の応答性を守ります。
"use client";
import { useState, useDeferredValue } from "react";
function Search({ items }: { items: Item[] }) {
const [query, setQuery] = useState("");
// 入力は即時反映、重いフィルタリングは「遅延」させて応答性を確保
const deferred = useDeferredValue(query);
const results = useMemo(() => filterItems(items, deferred), [items, deferred]);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} aria-label="検索" />
<ResultList results={results} />
</>
);
}
さらに効く手:
- イベントハンドラを軽く。 ハンドラ内で重い計算をせず、必要なら
scheduler.yield()(対応環境)やsetTimeoutでチャンクに分ける。 - デバウンス / スロットル。 連続入力・スクロール・リサイズはレート制限する。
- サードパーティ JS を遅延。 解析・チャット・広告は
next/scriptのstrategy="lazyOnload"などで後ろへ。 - DOM を小さく。 巨大リストは仮想化(ウィンドウイング)し、描画コストを抑える。
補足:リアルタイム編集や頻繁な再描画があるアプリ(共同編集・スコア入力など)では INP がそのまま「サクサク感」になります。状態更新の粒度設計が UX を左右します。
3. LCP を 2.5秒未満にする
LCP は「最大の要素(多くはヒーロー画像か見出し)」が見えるまでの時間です。効く順に。
3-1. 画像:next/image と priority
import Image from "next/image";
// LCP になるヒーロー画像には priority を付け、プリロードさせる
<Image
src="/hero.webp"
alt="サービスのヒーロー画像"
width={1200}
height={630}
priority // ← LCP 画像に必須。遅延読み込みを無効化しプリロード
sizes="100vw"
/>
next/image は自動で WebP/AVIF 変換・レスポンシブ生成・遅延読み込みを行います。ただし LCP 画像にだけは priority を付けます(付け忘れが頻出の不具合)。逆に、画面外の画像に priority を付けてはいけません。
3-2. フォント:next/font で FOUT/FOIT を消す
import { Noto_Sans_JP } from "next/font/google";
const noto = Noto_Sans_JP({
subsets: ["latin"],
display: "swap", // テキストを即表示(不可視待ちを避ける)
preload: true,
});
next/font はビルド時にフォントをセルフホストし、外部リクエストを消します。display: "swap" で「文字が見えない時間(FOIT)」を避けつつ、size-adjust 相当の調整で字形差によるズレ(CLS)も抑えます。
3-3. サーバー:TTFB と SSR/ストリーミング
- 静的化・キャッシュ。 可能な限り静的生成(SSG)/ ISR / PPR でサーバー処理を前倒しする。
- ストリーミングと
<Suspense>。 重いデータを待たずに、まず骨組み(とLCP要素)を流す。 preconnect/dns-prefetch。 必須の外部オリジン(CDN・計測)への接続を前倒しする。
4. CLS をゼロに近づける
CLS は「読み込み中に要素がガタッと動く量」です。原因は「寸法を予約していない後から現れる要素」に集約されます。
- 画像・動画・iframe・広告に明示寸法。
width/height(またはaspect-ratio)を必ず指定し、領域を先に確保する。 - フォント差し替えのズレ対策。
next/font+display: swap+ メトリクス調整。 - スクロールバーのズレ対策。
scrollbar-gutter: stableで出現時の横ズレを防ぐ(Tailwind v4 ガイド参照)。 - 動的挿入は上から押し下げない。 バナー・通知は領域を予約するか、レイアウトを動かさないオーバーレイにする。
- スケルトン UI を実コンテンツと同じ寸法で出す。
/* 後から入る要素も含め、領域を先に確保する */
.thumb {
aspect-ratio: 16 / 9; /* 画像読込前から高さが確定し、ズレない */
}
5. 計測:推測をやめ、現場の数値で改善する
5-1. フィールド計測(実ユーザー)を自前で取る
web-vitals ライブラリで実ユーザーの値を収集し、分析基盤へ送ります。Next.js では useReportWebVitals が使えます。
"use client";
import { useReportWebVitals } from "next/web-vitals";
export function WebVitals() {
useReportWebVitals((metric) => {
// 例:計測ビーコンで送信(sendBeacon は離脱時も送れる)
const body = JSON.stringify({ name: metric.name, value: metric.value, id: metric.id });
navigator.sendBeacon?.("/api/vitals", body);
});
return null;
}
- ラボ(Lighthouse / PageSpeed Insights) は再現・デバッグ用。CI に組み込み回帰を検出する。
- フィールド(CrUX / 自前 RUM) が合否の正。端末・回線・地域の分布を見て、75パーセンタイルを改善する。
5-2. 可観測性として運用する
- リリースごとに3指標を記録し、劣化したら気づけるようにする(回復性)。
- 指標をルート別・端末別に分解し、ボトルネックの局在を掴む。
- 「速さ」をダッシュボード化すると、改善が継続します。計測なき最適化は YAGNI 違反になりがちです。
6. Next.js 固有の効きどころ(早見表)
| 課題 | 手段 |
|---|---|
| 不要な JS が多い(INP) | RSC 化、"use client" を葉に限定、next/dynamic で遅延読み込み |
| LCP 画像が遅い | next/image + priority + 適切な sizes |
| フォントで遅延・ズレ | next/font + display: swap |
| サーバー応答が遅い | SSG / ISR / PPR、<Suspense> ストリーミング、キャッシュ |
| サードパーティが重い | next/script の strategy 調整、必要時のみ読み込み |
| レイアウトのズレ | 寸法予約、aspect-ratio、scrollbar-gutter: stable |
「とりあえず全部最適化」ではなく、計測で特定したボトルネックから順に潰すのが KISS です。
7. アンチパターン
- ❌ Lighthouse 100 で満足する。 合否はフィールド(実ユーザー75%ile)。ラボだけ見ない。
- ❌ ページ全体に
"use client"。 INP 悪化の主因。対話する葉だけに限定する。 - ❌ LCP 画像に
priorityを付け忘れる/画面外画像に付ける。 - ❌ 画像・広告枠の寸法を指定しない。 CLS が跳ね上がる。
- ❌ サードパーティ JS を
<head>で同期読み込み。 メインスレッドを塞ぐ。 - ❌ アクセシビリティを犠牲に速度を取る。 仮想化やスケルトンでも、フォーカスと読み上げ順は維持する。
8. FAQ(よくある質問)
Q. INP と FID は何が違う? A. FID は「最初の操作の遅延」だけを測りましたが、INP はページ滞在中の全操作の応答性を測ります。2024年3月に INP が FID を正式に置き換えました。
Q. Core Web Vitals は SEO 順位に影響する? A. ランキング要因の一つです。ただし「良好なら必ず上位」ではなく、コンテンツの質が前提。同等のコンテンツ間での差別化要因、かつ直帰率・CV を通じて間接的にも効きます。
Q. SPA と SSR、どちらが有利? A. 一般に SSR / RSC が LCP・INP に有利です。クライアントへ送る JS が減り、初期描画も速いためです。
Q. 画像最適化は next/image だけで十分?
A. 大半はカバーできますが、LCP 画像の priority、適切な sizes、フォーマット(WebP/AVIF)、寸法予約まで含めて初めて効果が出ます。
Q. どこから手を付けるべき? A. まず計測(フィールド)。次に最も失敗しやすい INP(JS削減)、続いて LCP(画像・フォント・SSR)、CLS(寸法予約)の順が費用対効果が高いです。
まとめ:速さは「測れる品質」であり、売上に効く
Core Web Vitals は、ユーザー体験を数値化したものです。改善はそのまま、検索流入・直帰率・コンバージョンの改善につながります。
- 計測(フィールド)から始める。 ラボだけを信じない。
- INP は JS を減らす。 RSC 化と
"use client"の局所化が本丸。 - LCP は画像・フォント・SSR。
priorityとnext/fontを確実に。 - CLS は寸法予約。
aspect-ratioとscrollbar-gutterでズレを消す。 - 可観測性として運用する。 劣化に気づける仕組みを持つ。
体感の速さは、プロダクトへの信頼そのものです。遅いサイトは、どれだけ機能が優れていても評価されません。
サイトの表示速度・Core Web Vitals の改善、あるいは高速で応答性の高いアプリの新規開発が必要な場合は、お気軽にご相談ください。 下記の事例では、複数人が同時に操作してもサクサク動く、応答性重視のリアルタイムアプリを設計・実装した過程を紹介しています。