フロントエンド性能最適化の実践:Core Web Vitals満点攻略ガイド
はじめに
ある日、上司から「うちのサイト、Lighthouseのスコアが60点しかないんだけど、1週間で90点にしてくれない?」と言われました。正直、冷や汗が出ました。Chrome DevToolsを開き、Lighthouseのレポートを見ると、LCP、FID(当時はまだありました)、CLSといった指標が赤や黄色で並んでいて、何から手をつければいいのか途方に暮れました。パフォーマンス最適化は、多くのフロントエンド開発者にとって、ある日突然降ってくる「厄介な追加タスク」になりがちです。
正直に言うと、私も最初は失敗ばかりでした。すべての画像を遅延読み込み(Lazy Loading)にしたら、かえってLCP(最大コンテンツの描画速度)が悪化してしまったり。Service Workerのキャッシュ戦略に丸2日費やしたのに、スコアが2点しか上がらなかったり。
この記事は、教科書的な理論解説ではありません。私が実際に手を動かし、2週間で目に見える効果を出した実戦ガイドです。どの最適化が最もコスパ(ROI)が良いか、絶対に踏んではいけない地雷は何か、そしてLighthouseスコアを60から90オーバーにするための具体的な優先順位をお伝えします。
第1章:Core Web Vitals 3大指標を理解する
専門用語に怯える必要はありません。要するに、**「表示は速いか?」「反応は良いか?」「画面はグラつかないか?」**の3点です。
Core Web Vitalsとは?
Googleが2020年に提唱したユーザー体験の指標で、SEOの検索順位に直接影響します。どんなに良いコンテンツを書いても、サイトが重ければ順位は下がります。2024年3月には大きなアップデートがあり、FID(First Input Delay)が廃止され、INP(Interaction to Next Paint)が正式なコア指標になりました。まだFIDを気にしているなら、情報をアップデートしましょう。
LCP - 最大コンテンツの描画 (Largest Contentful Paint)
LCPは、ページ内のメインコンテンツ(通常はヒーロー画像や大見出し、動画)が表示されるまでの時間を測ります。
- 2.5秒未満:良好(緑)
- 2.5〜4秒:要改善(黄)
- 4秒以上:不良(赤)
私はよくレストランに例えます。LCPは「メインディッシュが出てくるまでの時間」です。10分待っても料理が来なかったらイライラしますよね?
あるデータによると、読み込み時間が3秒から5秒に伸びると、直帰率は38%増加します。モバイルで3秒以上かかると、53%のユーザーが離脱します。LCPを改善すれば、コンバージョン率は7〜15%向上します。
INP - インタラクションへの応答性 (Interaction to Next Paint)
2024年3月からFIDに代わって導入された新指標です。クリック、キーボード入力、タップに対する応答速度を、**インタラクション全体(入力から描画完了まで)**を通して評価します。
- 200ms未満:良好(緑)
- 200〜500ms:要改善(黄)
- 500ms以上:不良(赤)
なぜGoogleは指標を変えたのか? FIDは「店員が注文を聞くまでの時間」しか見ていなかったのに対し、INPは「注文を聞いて、料理を出し終わるまで」を見るからです。
私が担当したあるプロジェクトではINPが650msもありました。ボタンを押してから反応があるまで0.5秒以上かかる状態です。原因はYouTubeの自動埋め込みでした。これを削除しただけでINPは220msに改善し、ユーザーからも「サクサク動くようになった」と好評でした。
CLS - 累積レイアウトシフト (Cumulative Layout Shift)
CLSは、視覚的な安定性を測ります。記事を読んでいる最中に広告が割り込んで来て、本文がガクッと下にズレた経験はありませんか? あれがCLSです。
- 0.1未満:良好(緑)
- 0.1〜0.25:要改善(黄)
- 0.25以上:不良(赤)
初めて計測した時、私のサイトのCLSは0.5でした。「0.5なら小さい数字じゃん」と思ったのですが、これは壊滅的な数値です。主な原因は、画像のサイズ指定忘れ、広告の割り込み、ウェブフォントの読み込みによるチラツキ(FOIT)です。
第2章:LCP最適化 - 最もコスパが良い施策
なぜLCPを再優先にするのか?
- 直感的にわかる:ユーザーが一番最初に「遅い」と感じる部分だから。
- 改善幅が大きい:5秒かかっていたものを2秒以下にするなど、劇的な改善が見込める。
- 技術が枯れている:画像最適化やCDNなど、確立された手法が多い。
- ROIが高い:少ない工数で大きな効果が出る。
Core Web Vitals最適化の完全フロー
LCP、INP、CLSを包括的に改善し、2週間でLighthouseスコアを90点以上にするためのステップバイステップガイド
⏱️ Estimated time: 2W
- 1
Step1: 優先度P0:画像最適化(LCP対策)
LCPの問題の70%以上は画像です。ここが一番の稼ぎどころです。
1. 次世代フォーマットの使用:
• AVIFはJPEGより41%圧縮率が高い
• WebPはJPEGより30%高い
• <picture>タグやNext.jsのImageコンポーネントで出し分ける
2. LCP画像の優先読み込み:
• 首屏(ファーストビュー)の画像は絶対にLazy Loadしてはいけない(loading="eager")
• Next.jsなら <Image priority /> を使う
• <link rel="preload"> タグを活用する
3. レスポンシブ画像:
• srcsetを使って、スマホには小さい画像を配信し、通信量を60%削減する
実例:ヒーロー画像をAVIFにして500KB→120KBに削減、LCPが4.2秒→2.1秒に改善。 - 2
Step2: 優先度P0:第三者スクリプトの遅延(INP対策)
外部スクリプトはINP悪化の主犯格です。
1. 非重要なスクリプトを遅らせる:
• defer属性を使うか、ロード後3秒待ってから読み込む
• Analyticsや広告タグは首屏に必須ではない
2. Facade(ファサード)パターンの適用:
• YouTubeの埋め込みなどは、最初は「ただの画像」を表示し、クリックされて初めて重いJSを読み込む
3. 自動埋め込みの排除:
• 不要なウィジェットを削除し、手動で制御する
実例:YouTubeの自動埋め込みを廃止し、INPが650ms→220msに改善。 - 3
Step3: 優先度P0:画像サイズ指定(CLS対策)
CLSは最も簡単に修正できる指標です。
1. 全画像にwidth/heightを指定:
• ブラウザが事前にスペースを確保できる
• CSSの aspect-ratio も有効
2. Base64インライン化の回避:
• 10KBを超える画像を埋め込むとHTMLが肥大化し、レンダリングをブロックする
実例:全画像にサイズを指定しただけで、CLSが0.35→0.05に改善。 - 4
Step4: 優先度P1:コード分割とリソース最適化
1. ルーティング単位のコード分割:
• React.lazy() や VueのdefineAsyncComponent()
• 今必要なページのリソースだけを読み込む
2. 長いタスク(Long Task)の分割:
• requestIdleCallback を使い、メインスレッドをブロックしない
• 50ms以上の処理は分割する
3. CSS最適化:
• クリティカルなCSSはインライン化
• それ以外は遅延読み込み - 5
Step5: 優先度P1:フォントとサーバー最適化
1. フォント最適化:
• font-display: swap を指定し、読み込み中のテキスト非表示(FOIT)を防ぐ
• 重要なフォントはpreloadする
2. サーバー最適化:
• 静的リソースはすべてCDNに載せる
• SSG(静的サイト生成)やSSR(サーバーサイドレンダリング)でTTFBを短縮 - 6
Step6: 優先度P2:高度な最適化
ROIは中程度ですが、さらなる高みを目指すなら:
1. Web Workers:重い計算処理を別スレッドに逃がす
2. 高度なキャッシュ戦略:Service Workerなど
1. 画像最適化(ROI: ⭐⭐⭐⭐⭐)
LCP改善の王道です。私はいつもここから始めます。
a) 次世代フォーマットの利用
JPEGをAVIFにするだけで、画質を保ったまま画像サイズを劇的に減らせます。
<!-- 方法1:pictureタグでブラウザに選ばせる -->
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image"> <!-- フォールバック用 -->
</picture>
// 方法2:Next.jsなら自動でやってくれる
import Image from 'next/image'
<Image
src="/hero.jpg"
width={1200}
height={600}
priority // 重要:LCP画像には必ずpriorityをつける!
/>
実戦データ:
- AVIFはJPEGより41%圧縮率が高い
- WebPはJPEGより30%高い
- あるECサイトでヒーロー画像をAVIF化したところ、500KBが120KBになり、LCPが4.2秒から2.1秒へ半減しました。
b) 画像の遅延読み込み(LCP画像を除く!)
ここで私は大きな失敗をしました。LCP画像(ファーストビューの一番大きな画像)まで loading="lazy" にしてしまったのです。これでは読み込みが後回しにされ、スコアが下がります。
<!-- ❌ 間違い:LCP画像を遅延読み込みしてはいけない -->
<img src="hero.jpg" loading="lazy">
<!-- ✅ 正解:首屏画像はeager、それ以外はlazy -->
<img src="hero.jpg" loading="eager"> <!-- LCP画像 -->
<img src="product1.jpg" loading="lazy"> <!-- スクロールして見える画像 -->
<img src="product2.jpg" loading="lazy">
重要ルール:ファーストビューに入る画像は、1枚たりとも遅延読み込みしてはいけません。
c) サーバー応答時間の短縮(ROI: ⭐⭐⭐⭐)
すべての静的リソース(画像、CSS、JS)をCDNに載せるのは基本中の基本です。HTML自体もCDNキャッシュ(SSG/ISR)できれば最強です。TTFB(Time to First Byte)が200msから50msに短縮された事例もあります。
第3章:INP最適化 - サクサク感を出す
第三者スクリプト(Google Analytics, 広告タグ, チャットボットなど)は、INPを悪化させる最大の要因です。
1. JavaScript実行の最適化(ROI: ⭐⭐⭐⭐)
a) コード分割(Code Splitting)
「必要な時に、必要なコードだけ」読み込むのが鉄則です。
// React - ルート単位で分割
import { lazy, Suspense } from 'react'
const Dashboard = lazy(() => import('./Dashboard'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
)
}
効果:ある管理画面で導入したところ、バンドルサイズが1.2MBから200KB(初期ロード)に減り、INPが450msから180msに改善しました。
b) 長いタスク(Long Task)を分割する
メインスレッドを50ms以上ブロックする処理は「長いタスク」と見なされ、ユーザーの入力を阻害します。requestIdleCallback などを使って細切れにしましょう。
2. 第三者スクリプトの制御(ROI: ⭐⭐⭐⭐⭐)
外部スクリプトは容赦なくリソースを食います。
a) 重要でないスクリプトは遅らせる
<!-- ❌ ページレンダリングをブロックする -->
<script src="analytics.js"></script>
<!-- ✅ 読み込み完了まで待つ -->
<script defer src="analytics.js"></script>
<!-- ✅ もっと積極的に、ロード後3秒待つ -->
<script>
window.addEventListener('load', () => {
setTimeout(() => {
// 3秒後に分析ツールを読み込む
const script = document.createElement('script')
script.src = 'analytics.js'
document.body.appendChild(script)
}, 3000)
})
</script>
b) ファサード(Facade)パターン
YouTubeの埋め込みプレイヤーは非常に重い(1MB以上)です。最初は「再生ボタン付きの画像」だけを表示し、クリックされたら初めて本物のプレイヤーを読み込むようにします。
これでINPが劇的に改善します。
第4章:CLS最適化 - 画面を安定させる
CLSはユーザーをイラつかせますが、修正は一番簡単です。
1. 画像にサイズを指定する(ROI: ⭐⭐⭐⭐⭐)
これがCLS原因のナンバーワンです。
<!-- ❌ サイズ指定なし:読み込み後にガクッとズレる -->
<img src="photo.jpg" alt="Photo">
<!-- ✅ 幅と高さを指定:ブラウザがスペースを確保する -->
<img src="photo.jpg" width="800" height="600" alt="Photo">
<!-- ✅ CSS aspect-ratio も有効 -->
<style>
img { aspect-ratio: 16 / 9; width: 100%; }
</style>
2. フォント読み込みの最適化(ROI: ⭐⭐⭐⭐)
ウェブフォントが読み込まれる瞬間にテキストの幅が変わってレイアウトがズレることがあります。
font-display: swap を使いましょう。
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* フォールバックフォントを即座に表示 */
}
3. 動的コンテンツのスペース確保
広告バナーなどが後から挿入される場合、あらかじめその分の高さ(min-height)をCSSで確保しておきます。骨格だけを表示する「スケルトンスクリーン」も有効です。
第5章:総合実戦 - 優先順位マトリックス
いきなり全部やるのは無理です。以下の優先順位で進めてください。
| 施策 | ROI(コスパ) | 難易度 | 優先度 |
|---|---|---|---|
| 画像にwidth/height追加 | 激高 | 極低 | P0 |
| 画像フォーマット変換 (AVIF) | 激高 | 低 | P0 |
| LCP画像の最適化 (priority) | 激高 | 低 | P0 |
| 第三者スクリプト遅延 | 激高 | 低 | P0 |
| フォント最適化 | 高 | 低 | P1 |
| コード分割 | 高 | 中 | P1 |
| CDN導入 | 高 | 低 | P1 |
| SSR/SSG導入 | 中 | 高 | P2 |
| Web Workers | 低 | 高 | P3 |
避けるべき「やってはいけない」こと
- LCP画像のLazy Load:絶対にダメです。スコアが下がります。
- 本番環境以外での計測:開発環境や拡張機能が入ったChromeで測っても無意味です。必ずシークレットモードかWebPageTestを使いましょう。
- モバイル計測の無視:トラフィックの70%以上はモバイルです。デスクトップのスコアだけ良くても意味がありません。
まとめ
パフォーマンス最適化は、一度やれば終わりのタスクではありません。しかし、正しい手順(画像の最適化、スクリプトの遅延、レイアウトの固定)を踏めば、2週間でLighthouse 90点は決して不可能な数字ではありません。
まずはP0の施策から試してみてください。きっと数字の変化に驚くはずです。
FAQ
Core Web Vitalsの合格ラインは?
LCPを改善する一番手っ取り早い方法は?
INPの改善方法は?
CLSが悪化する主な原因は?
LCP画像にLazy Loadを使ってはいけない理由は?
性能計測のおすすめツールは?
5 min read · 公開日: 2025年11月24日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド

コメント
GitHubアカウントでログインしてコメントできます