Core Web Vitalsとパフォーマンス指標
Webパフォーマンスは「体感速度」と「検索順位」の両方に直結します。 Googleは2021年からCore Web Vitalsをランキングシグナルに組み込み、 2024年3月にはFID(First Input Delay)をINP(Interaction to Next Paint)に置き換えました。 2026年現在、Next.jsで注視すべきパフォーマンス指標は以下の5つです。
| 指標 | 正式名称 | 閾値(Good) | 測定対象 |
|---|---|---|---|
| LCP | Largest Contentful Paint | ≤ 2.5秒 | ビューポート内最大要素の描画完了時間 |
| INP | Interaction to Next Paint | ≤ 200ms | ユーザー操作から次の描画までの遅延 |
| CLS | Cumulative Layout Shift | < 0.1 | レイアウトの視覚的な安定性 |
| FCP | First Contentful Paint | ≤ 1.8秒 | 最初のコンテンツが描画されるまでの時間 |
| TTFB | Time to First Byte | ≤ 800ms | リクエストから最初のバイトを受信するまでの時間 |
LCP最適化 — 最大コンテンツの高速描画
LCP(Largest Contentful Paint)は、ビューポート内で最も大きな要素(通常はヒーロー画像やh1テキスト)が 描画完了するまでの時間です。目標は2.5秒以内。 LCPの改善は、TTFB短縮→リソース発見→リソースロード→レンダリングの4段階で考えます。
graph LR
A[TTFB短縮] -->|サーバー応答| B[リソース発見]
B -->|preload/priority| C[リソースロード]
C -->|画像最適化| D[レンダリング]
D -->|クリティカルCSS| E[LCP完了]
style A fill:#3b82f6,stroke:#1d4ed8,color:#fff
style B fill:#8b5cf6,stroke:#6d28d9,color:#fff
style C fill:#f97316,stroke:#ea580c,color:#fff
style D fill:#10b981,stroke:#059669,color:#fff
style E fill:#ec4899,stroke:#db2777,color:#fff画像のpriority属性とpreload
ファーストビューに表示されるヒーロー画像には、next/imageのpriority属性を付与します。
これにより、Next.jsは自動的に<link rel="preload">タグをHTMLの<head>に挿入し、
ブラウザが画像を最優先でダウンロードします。
import Image from 'next/image'
// ファーストビューのヒーロー画像にはpriorityを付与
export default function HeroSection() {
return (
<Image
src="/hero.webp"
alt="メインビジュアル"
width={1200}
height={630}
priority // preloadヒントが自動挿入される
sizes="100vw" // ビューポート幅に応じた最適サイズを配信
/>
)
} クリティカルCSSとフォントプリロード
Next.jsのApp Routerは、各ページで使用されるCSSを自動的にインライン化(クリティカルCSS抽出)します。
これにより、外部CSSファイルの読み込みによるレンダリングブロックを回避できます。
加えて、next/fontを使用するとフォントファイルがビルド時にセルフホスティングされ、
外部CDNへのリクエストが不要になります。
// app/layout.tsx — フォントプリロードによるLCP改善
import { Noto_Sans_JP } from 'next/font/google'
const notoSansJP = Noto_Sans_JP({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap', // フォント読み込み中はフォールバックを表示
preload: true, // プリロードで読み込みを高速化
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja" className={notoSansJP.className}>
<body>{children}</body>
</html>
)
} INP最適化 — インタラクションの応答性
INP(Interaction to Next Paint)は、ユーザーがクリック・タップ・キー入力を行ってから 次のフレームが描画されるまでの遅延を測定します。目標は200ms以内。 INPが悪化する主な原因は、メインスレッドをブロックする長時間タスク(Long Tasks)です。
長時間タスクの分割
50msを超えるJavaScriptタスクは「Long Task」と分類され、INPを悪化させます。
重い処理はscheduler.yield()やsetTimeoutで分割し、
メインスレッドに制御を返すことで、ユーザー操作への応答性を維持します。
// 長時間タスクを分割してINPを改善
async function processLargeList(items: Item[]) {
const CHUNK_SIZE = 100
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE)
processChunk(chunk)
// メインスレッドに制御を返す(ブラウザが描画・入力処理を実行可能に)
await scheduler.yield()
}
} Server Componentsによるクライアントサイドjs削減
INP改善の最も効果的な手段は、そもそもクライアントに送るJavaScriptを減らすことです。 Server Componentsはサーバー側でレンダリングされ、そのJavaScriptバンドルはクライアントに一切送信されません。 インタラクティブ性が不要なコンポーネントをServer Componentsにするだけで、 メインスレッドの負荷が大幅に軽減されます。
CLS最適化 — レイアウトの安定性
CLS(Cumulative Layout Shift)は、ページ読み込み中にレイアウトが予期せず移動する量を測定します。 目標は0.1未満。CLSの主な原因は、サイズが未指定の画像と、 フォントの読み込みによるテキストのリフローです。 Next.jsは、この2大原因に対する組み込みソリューションを提供しています。
next/imageによる自動サイズ確保
next/imageコンポーネントは、widthとheightを必須propsとして要求します。
これらの値からアスペクト比が自動計算され、画像の読み込み前にブラウザがスペースを確保するため、
レイアウトシフトが発生しません。
next/fontによるCLS=0の実現
next/fontは以下の3つのメカニズムでフォント起因のCLSを完全に排除します。
| メカニズム | 効果 | 従来の方法との違い |
|---|---|---|
| セルフホスティング | Google Fontsへの外部リクエストを排除し、TTFB短縮 | 従来: CDNからの読み込みで追加レイテンシが発生 |
| フォールバック自動調整 | size-adjustプロパティでフォールバックフォントのサイズを本番フォントに一致させる | 従来: フォント切替時にテキストがリフロー |
| サブセット化 | 使用する文字だけを含むフォントファイルを生成し、ファイルサイズ削減 | 従来: 全文字セットをダウンロード(日本語フォントは特に巨大) |
next/imageの最適化テクニック
next/imageは、単なる画像表示コンポーネントではなく、
LCP・CLS・帯域幅の最適化を自動で行う高機能コンポーネントです。
主要な最適化プロパティを整理します。
import Image from 'next/image'
// 最適化を最大限活用した画像コンポーネント
export function OptimizedImage() {
return (
<Image
src="/product-hero.jpg"
alt="製品画像"
width={800}
height={600}
priority // ファーストビュー画像はpreload
sizes="(max-width: 768px) 100vw, 800px" // レスポンシブサイズ指定
placeholder="blur" // 読み込み中にぼかしプレースホルダー表示
blurDataURL="data:image/..." // カスタムぼかし画像(省略時は自動生成)
quality={80} // 画質80%(デフォルト75)
/>
)
}
// fillモード: 親コンテナに合わせてサイズ自動調整
export function ResponsiveImage() {
return (
<div className="relative aspect-video">
<Image
src="/banner.jpg"
alt="バナー"
fill // 親要素を基準にサイズ決定
sizes="100vw"
className="object-cover" // アスペクト比を維持してカバー
/>
</div>
)
} next/fontによるCLSゼロの実現
Webフォントの読み込みは、CLS悪化の主要因の一つです。
ブラウザはフォントファイルのダウンロード完了まで代替フォントで表示し、
完了後に本番フォントに切り替える際、文字幅の差異によりレイアウトシフトが発生します。
next/fontはこの問題を根本的に解決します。
// next/font/google — Google Fontsのセルフホスティング
import { Noto_Sans_JP } from 'next/font/google'
import { JetBrains_Mono } from 'next/font/google'
// 日本語本文フォント
const notoSansJP = Noto_Sans_JP({
subsets: ['latin'], // ラテン文字サブセットのみ事前ロード
weight: ['400', '700'], // 必要なウェイトのみ
display: 'swap', // FOUTで表示を高速化
fallback: ['Hiragino Sans', 'sans-serif'], // フォールバック指定
adjustFontFallback: true, // フォールバックのsize-adjustを自動計算
})
// コード用等幅フォント
const jetBrainsMono = JetBrains_Mono({
subsets: ['latin'],
variable: '--font-mono', // CSS変数として公開
display: 'swap',
})
// next/font/local — ローカルフォントファイルの使用
import localFont from 'next/font/local'
const customFont = localFont({
src: [
{ path: './fonts/custom-regular.woff2', weight: '400' },
{ path: './fonts/custom-bold.woff2', weight: '700' },
],
display: 'swap',
}) バンドル分析と最適化
パフォーマンス改善の出発点は計測です。 何が大きいかを知らなければ、何を削るべきかも分かりません。 Next.jsエコシステムでは、バンドルサイズの分析と最適化のための強力なツールが揃っています。
@next/bundle-analyzerによる可視化
// next.config.ts — バンドル分析の設定
import withBundleAnalyzer from '@next/bundle-analyzer'
const config = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})({
// Next.jsの設定
})
export default config
// 実行コマンド
// ANALYZE=true npm run build
// → ブラウザに3つのレポート(nodejs / edge / browser)が自動表示 optimizePackageImportsによるTree Shaking強化
一部のnpmパッケージはバレルファイル(index.tsで全モジュールを再エクスポート)を使用しており、
Tree Shakingが効かないケースがあります。
optimizePackageImports設定により、名前付きインポートを個別モジュールへの直接インポートに自動変換し、
不要なコードを確実に除外します。
// next.config.ts
const nextConfig = {
experimental: {
optimizePackageImports: [
'lucide-react', // アイコンライブラリ
'@heroicons/react', // アイコンライブラリ
'date-fns', // 日付ユーティリティ
'lodash-es', // ユーティリティライブラリ
'recharts', // チャートライブラリ
],
},
}
// before: import { Calendar } from 'lucide-react' → 全アイコンをバンドル
// after: import Calendar from 'lucide-react/dist/esm/icons/calendar' に自動変換 dynamic importによるコード分割
初期表示に不要な重いコンポーネントは、next/dynamicで遅延ロードします。
これにより、初期バンドルサイズを削減し、LCPとTTIを改善できます。
import dynamic from 'next/dynamic'
// 重いチャートライブラリを遅延ロード
const HeavyChart = dynamic(() => import('./HeavyChart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-800 rounded" />,
ssr: false, // クライアントサイドのみでレンダリング
})
// 条件付きインポート(モーダルなど、開くまで不要なもの)
const Modal = dynamic(() => import('./Modal'))
export default function Dashboard() {
const [showModal, setShowModal] = useState(false)
return (
<>
<HeavyChart data={chartData} />
{showModal && <Modal onClose={() => setShowModal(false)} />}
</>
)
} serverExternalPackagesによるバンドル除外
サーバーサイドでのみ使用する大型パッケージ(ORM、PDF生成など)は、
serverExternalPackagesでバンドルから除外し、Node.jsのrequireで動的解決させます。
// next.config.ts
const nextConfig = {
serverExternalPackages: [
'prisma', // Prisma ORM
'@prisma/client', // Prismaクライアント
'sharp', // 画像処理
'puppeteer', // ヘッドレスブラウザ
],
} React Compilerによる自動メモ化
React Compiler(旧React Forget)は、Reactコンポーネントとフックを自動的にメモ化するビルド時コンパイラです。
手動でuseMemo、useCallback、React.memoを書く必要がなくなり、
パフォーマンスと開発者体験の両方が向上します。
graph TD
A[Reactソースコード] -->|React Compiler| B[最適化されたコード]
B --> C[自動useMemo挿入]
B --> D[自動useCallback挿入]
B --> E[自動React.memo適用]
C --> F[不要な再計算を排除]
D --> G[不要な関数再生成を排除]
E --> H[不要な再レンダリングを排除]
F --> I[パフォーマンス向上]
G --> I
H --> I
style A fill:#3b82f6,stroke:#1d4ed8,color:#fff
style B fill:#8b5cf6,stroke:#6d28d9,color:#fff
style I fill:#10b981,stroke:#059669,color:#fffMeta社内での実データによると、React Compilerは以下の成果を達成しています。
| 指標 | 改善幅 | 備考 |
|---|---|---|
| Instagram Webの全体速度 | 最大12%高速化 | プロダクション環境での測定 |
| 特定インタラクション | 2.5倍以上高速化 | フィード操作やコメント投稿など |
| Quest Store(VR) | 大幅なINP改善 | Meta Quest向けストアアプリ |
Next.jsでのReact Compiler設定
// next.config.ts — React Compilerの有効化
const nextConfig = {
experimental: {
reactCompiler: true, // React Compilerを有効化
},
}
// Babelプラグインとしてのインストール
// npm install -D babel-plugin-react-compiler
// オプション: 段階的導入(特定ディレクトリのみ対象)
const nextConfig = {
experimental: {
reactCompiler: {
compilationMode: 'annotation', // "use memo"ディレクティブ付きのみ
},
},
} 計測と改善のサイクル
パフォーマンス最適化は一度やって終わりではありません。 計測 → 分析 → 改善 → 再計測のサイクルを継続的に回すことが重要です。 Vercelは、このサイクルを支援する2つのツールを提供しています。
Vercel Speed Insights
Real User Monitoring(RUM)として、実際のユーザーのCore Web Vitals(LCP, INP, CLS, FCP, TTFB)を リアルタイムで収集・可視化します。Labデータ(Lighthouse等)では見えない、 実際のネットワーク環境やデバイス性能を反映したフィールドデータを取得できます。
// app/layout.tsx — Speed Insightsの導入
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>
{children}
<SpeedInsights /> {/* RUMデータを自動収集 */}
</body>
</html>
)
} Vercel Web Analytics
ページビュー数、ユニークビジター、リファラー、地域などのトラフィック分析を プライバシーファーストで提供します。Cookie不使用のため、GDPRバナーも不要です。 Speed InsightsのパフォーマンスデータとWeb Analyticsのトラフィックデータを組み合わせることで、 「どのページが遅くて、かつアクセスが多いか」を特定し、最適化の優先順位を決定できます。
// app/layout.tsx — Web Analyticsの導入
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>
{children}
<Analytics /> {/* トラフィック分析 */}
<SpeedInsights /> {/* パフォーマンス計測 */}
</body>
</html>
)
} まとめ — パフォーマンスは機能である
パフォーマンスは後から考えるものではなく、設計段階から組み込むべき機能です。
Next.jsは、Core Web Vitalsの各指標に対する組み込みソリューション(next/image, next/font,
Server Components, Streaming)を提供し、開発者が意識せずとも高いパフォーマンスを達成できるよう設計されています。
React Compilerの自動メモ化は、これまで開発者が手動で行ってきた最適化を自動化する画期的なツールです。 Meta社内のデータが示すように、既存アプリケーションにも大きな効果が期待できます。
そして最も重要なのは、計測なき最適化は盲目であるということ。 Vercel Speed InsightsとWeb Analyticsによる継続的な計測サイクルを確立し、 実ユーザーのデータに基づいた改善を積み重ねましょう。 次章では、Next.jsのエコシステムと開発ツール — Turbopack、Turborepo、AI SDKについて解説します。
理解度チェック
Core Web Vitalsの3指標として正しい組み合わせはどれですか?
キーボード: 1〜4 で選択、Enter で回答