.png?q=75&fm=webp)
LCP改善における画像最適化の全手法を徹底解説
Webサイトの表示速度がSEOの明暗を分ける時代になりました。
特にLCP(Largest Contentful Paint)は、Googleが重視する最重要指標です。
実は、LCP要素の75%以上が画像。つまり、画像を最適化すれば、LCPは劇的に改善します。
この記事では、WebP/AVIF変換、preload、fetchpriority、CDN活用など、実証済みの施策をコード付きで完全解説。
初心者から上級者まで、今日から実践できる具体的な方法をお伝えします。PageSpeed Insights 90点超えも夢ではありません!
LCP改善で画像最適化が最重要な理由
Webサイトの表示速度は、もはやユーザー体験だけでなく、SEOの成否を分ける決定的な要因となっています。
特に2024年以降、GoogleはCore Web Vitals、中でもLCP(Largest Contentful Paint)を重要なランキング要素として扱うようになりました。
衝撃的な統計データ:LCP要素の約75%以上が画像です。
つまり、LCP改善の鍵は画像最適化にあると言っても過言ではありません。実際、適切な画像最適化を行うことで、多くのサイトがLCPを2秒以上短縮し、検索順位の向上とコンバージョン率の改善を実現しています。
LCPと画像の基礎知識
LCP(Largest Contentful Paint)とは、ウェブページの読み込み速度を測る重要な指標の一つです。
ページ内で最も大きなコンテンツ(画像、動画、テキストブロックなど)が表示されるまでの時間を計測します。
Googleが重視するCore Web Vitalsの一つで、ユーザー体験とSEO評価に直接影響します。
理想的なLCPは2.5秒以内で、これを超えるとページの読み込みが遅いと判断され、検索順位が下がる可能性があります。
画像の最適化、サーバー応答時間の改善、不要なJavaScriptの削減などで改善できます。
参考記事:LCPとは?
なぜ画像がLCPのボトルネックになるのか
前述の通り、Webページの75%以上でLCP要素は画像です。
特にヒーロー画像(ページ上部の大きなメイン画像)や商品画像がLCP要素になるケースが圧倒的に多いのです。
LCP要素となる画像の典型例
- トップページのヒーロー画像・スライダー画像
- 記事ページのアイキャッチ画像
- 商品ページのメイン商品画像
- ランディングページのキービジュアル
これらの画像が適切に最適化されていないと、LCPは確実に悪化します。
画像読み込みがLCPに与える影響の内訳
LCPの時間は、以下の4つのフェーズで構成されています。
フェーズ | 説明 | LCP全体に占める割合 |
|---|---|---|
TTFB(Time to First Byte) | サーバーがHTMLを返すまでの時間 | 約20-30% |
リソース読み込み遅延 | HTMLパース後、画像読み込み開始までの遅延 | 約10-20% |
リソース読み込み時間 | 画像ファイルのダウンロード時間 | 約50-60% |
要素レンダリング時間 | ダウンロード後、画面に描画されるまでの時間 | 約5-10% |
このデータから分かるように、リソース読み込み時間が全体の半分以上を占めています。
つまり、画像ファイルのサイズを削減し、読み込み速度を上げることが、LCP改善の最大のポイントなのです。
しかし、ここで重要な事実があります。
Google web.devが指摘する重要なポイント
Google公式のweb.devは、次のように警告しています。
「LCPを効果的に最適化するには、画像の最適化だけでなく、ページの読み込み全体を総合的に検討することが必要」
つまり、画像を軽量化しただけでは不十分です。
- サーバー応答時間(TTFB)の改善
- 画像読み込みの優先順位制御(preload、fetchpriority)
- レンダリングをブロックするリソースの除去
これらを組み合わせた総合的なアプローチが必要になります。
本記事では、これらすべてを網羅した12の施策を解説していきます。
LCP画像を特定する方法
LCP改善の第一歩は、どの要素がLCPとして認識されているかを正確に特定することです。
モバイルとデスクトップで異なる要素がLCPになることも多いため、両方を確認する必要があります。
方法1:PageSpeed Insightsで確認する
最も手軽で正確な方法は、Google公式の無料ツール「PageSpeed Insights」を使う方法です。
手順
- PageSpeed Insightsにアクセス
- 調査したいページのURLを入力
- 「分析」ボタンをクリック
- 結果ページで「診断」セクションまでスクロール
- 「"最大コンテンツの描画"要素」を展開
ここに表示される要素が、あなたのページのLCP要素です。
画像の場合は、画像のURLとサイズも表示されます。
重要: 「モバイル」と「パソコン」のタブを切り替えて、両方のLCP要素を確認してください。
多くの場合、デバイスによって異なる画像がLCP要素になります。
方法2:Chrome DevToolsで確認する(詳細分析向け)
より詳細な情報が必要な場合は、Chrome DevToolsのパフォーマンスタブを使います。
手順
- 調査したいページをChromeで開く
- F12キーを押してDevToolsを開く
- 「Performance」タブを選択
- 左上の「Web Vitals」チェックボックスにチェックを入れる
- リロードボタン(円形矢印アイコン)をクリックしてパフォーマンス記録を開始
- 記録が完了したら、「Timings」セクションで「LCP」にカーソルを合わせる
ネットワークタブでは読み込み時間が、タイミングタブではLCP要素そのものが確認できます。
LCP改善のための画像最適化12施策
ここからは、LCPを改善するための具体的な施策を、効果の高い順に解説します。
【優先度高】次世代フォーマット(WebP/AVIF)への変換
項目 | 内容 |
|---|---|
期待効果 | ファイルサイズ 30〜70% 削減 |
難易度 | 初級 〜 中級 |
実装時間 | 1 〜 3 時間 |
なぜ次世代フォーマットが効果的なのか
従来のJPEGやPNGと比較して、WebPとAVIFは以下のような圧倒的な優位性があります。
フォーマット | 圧縮率 | 画質 | ブラウザ対応(2025年12月時点) | エンコード速度 |
|---|---|---|---|---|
JPEG | 基準(100%) | 良好 | 100% | 高速 |
PNG | 低い(150-200%) | 可逆圧縮 | 100% | 高速 |
WebP | 高い(50-70%) | 良好〜優秀 | 98%+(ほぼ全環境) | 中速 |
AVIF | 非常に高い(40-60%) | 優秀 | 95%+(IE以外) | 低速 |
WebPは、JPEGと同等の画質を保ちながら、ファイルサイズを30-50%削減できます。
AVIFはさらに高い圧縮率を誇りますが、エンコード時間が長く、一部の古いデバイスでは描画負荷が高い場合があります。
参考記事:次世代フォーマット画像とは
WebP vs AVIF:どちらを選ぶべきか
WebPを主軸とし、AVIFをオプションで併用する戦略が最もバランスが良いと言えます。
WebPを推奨する理由
- ブラウザ対応率が98%以上で実質的にユニバーサル
- エンコード速度が速く、大量の画像変換に適している
- 十分な圧縮率(JPEG比で30-50%削減)を実現
AVIFを併用すべきケース
- 高画質が求められる商品画像
- ポートフォリオサイトのビジュアル
- ブランドイメージを重視するサイトのヒーロー画像
実装方法1:HTMLでの基本実装
最も汎用性が高く、どんなサイトでも使える方法です。
<picture>
<!-- 最優先:AVIF(対応ブラウザのみ) -->
<source type="image/avif" srcset="hero-image.avif">
<!-- 次点:WebP(ほぼ全ブラウザ) -->
<source type="image/webp" srcset="hero-image.webp">
<!-- フォールバック:JPEG(全ブラウザ) -->
<img src="hero-image.jpg"
alt="サイトのメインビジュアル"
width="1200"
height="600"
loading="eager"
fetchpriority="high">
</picture>このコードのポイント
- ブラウザは上から順に対応フォーマットをチェックし、最初に対応しているものを選択
- 古いブラウザでも必ずJPEGが表示される(互換性100%)
- width/height属性でCLS(Cumulative Layout Shift)も防止
実装方法2:WordPressでの実装
WordPressを使用している場合、プラグインで自動変換が可能です。
推奨プラグイン
プラグイン名 | WebP対応 | AVIF対応 | 価格 | 特徴 |
|---|---|---|---|---|
EWWW Image Optimizer | ○ | ○ | 無料/有料 | 既存画像の一括変換が得意 |
ShortPixel | ○ | ○ | 無料枠あり | クラウド変換で高速 |
Imagify | ○ | ○ | 無料枠あり | 圧縮率のバランスが良い |
WebP Converter for Media | ○ | × | 無料 | WebP専門で軽量 |
EWWW Image Optimizerの設定手順
- WordPressダッシュボードから「プラグイン」→「新規追加」
- 「EWWW Image Optimizer」を検索してインストール
- 「設定」→「EWWW Image Optimizer」→「基本」タブ
- 「WebP変換」にチェック(有料版ならAVIFも選択可能)
- 「変更を保存」をクリック
- 「メディア」→「一括最適化」で既存画像を一括変換
functions.phpへの追加(picture要素で出力する場合)
// アイキャッチ画像をpicture要素で出力する関数
function output_responsive_thumbnail() {
if (has_post_thumbnail()) {
$thumbnail_id = get_post_thumbnail_id();
$thumbnail_url = wp_get_attachment_image_src($thumbnail_id, 'full')[0];
$thumbnail_alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);
// WebP/AVIFのURLを生成(EWWW Image Optimizerの変換後)
$webp_url = preg_replace('/\.(jpg|jpeg|png)$/i', '.webp', $thumbnail_url);
$avif_url = preg_replace('/\.(jpg|jpeg|png)$/i', '.avif', $thumbnail_url);
echo '<picture>';
if (file_exists(str_replace(home_url('/'), ABSPATH, $avif_url))) {
echo '<source type="image/avif" srcset="' . esc_url($avif_url) . '">';
}
if (file_exists(str_replace(home_url('/'), ABSPATH, $webp_url))) {
echo '<source type="image/webp" srcset="' . esc_url($webp_url) . '">';
}
echo '<img src="' . esc_url($thumbnail_url) . '" alt="' . esc_attr($thumbnail_alt) . '" fetchpriority="high">';
echo '</picture>';
}
}実装方法3:Next.js/Nuxt.jsでの実装
モダンなJavaScriptフレームワークを使用している場合、フレームワークの画像最適化機能を活用できます。
Next.jsの場合
import Image from 'next/image'
export default function HeroSection() {
return (
<Image
src="/images/hero.jpg"
width={1200}
height={600}
alt="ヒーロー画像"
priority // LCP画像の場合は必須
quality={85} // 画質設定(デフォルト75)
placeholder="blur" // ぼかしプレースホルダー
blurDataURL="data:image/jpeg;base64,..." // 低解像度プレースホルダー
/>
)
}Next.js の next.config.js 設定
module.exports = {
images: {
formats: ['image/avif', 'image/webp'], // 自動的にAVIF/WebPに変換
deviceSizes: [640, 750, 828, 1080, 1200, 1920], // レスポンシブサイズ
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // アイコンサイズ
},
}Nuxt.jsの場合
<template>
<nuxt-img
src="/images/hero.jpg"
width="1200"
height="600"
alt="ヒーロー画像"
format="webp"
quality="85"
loading="eager"
fetchpriority="high"
/>
</template>Nuxt.jsでは、@nuxt/imageモジュールをインストールすることで、自動的にWebP変換とレスポンシブ対応が可能になります。
npm install @nuxt/imageよくある失敗パターンと対策
失敗1:フォールバック画像を用意していない
古いブラウザ(IE11など)ではWebP/AVIFが表示されず、画像が完全に欠落してしまいます。必ず<img>タグでJPEG/PNGのフォールバックを用意してください。
失敗2:AVIFのエンコード時間を考慮していない
数百枚の画像をAVIFに変換すると、数時間から数日かかる場合があります。本番環境での一括変換は避け、ステージング環境で事前に変換しておきましょう。
失敗3:品質設定が不適切
WebPの品質を50以下に設定すると、明らかに画質劣化が見えます。
LCP対象のヒーロー画像は品質75-85を推奨します。
正しいアプローチ
- 重要な画像(LCP対象):品質80-85
- 一般的な画像:品質75-80
- サムネイル画像:品質60-70
【優先度高】LCP画像のプリロード(preload)
項目 | 内容 |
|---|---|
期待効果 | LCP 0.3〜1.0秒 改善 |
難易度 | 中級 |
実装時間 | 30分 〜 1時間 |
preloadが効果的な理由
通常、ブラウザは以下の順序でリソースを読み込みます。
- HTMLのダウンロード
- HTMLのパース(解析)
- CSSのダウンロード
- CSSのパース
- 画像の発見とダウンロード開始
つまり、LCP画像の読み込みは、HTML・CSSが完全に処理された後に初めて開始されます。
この遅延が、LCPを悪化させる主要因の一つです。
preloadを使うと、以下のような流れに変わります。
- HTMLのダウンロード開始と同時に画像のダウンロードも開始
- HTMLのパース
- CSSのダウンロード・パース
- 画像はすでにダウンロード済み → 即座に表示
この差が、0.3-1.0秒のLCP改善につながります。
preloadすべき画像の判断基準
preloadを使うべき画像
- ファーストビュー内の最大画像(LCP要素)
- ヒーロー画像
- 記事のアイキャッチ画像(記事ページの場合)
preloadを使うべきでない画像
- ファーストビュー外の画像
- 2枚目以降のスライダー画像
- サムネイル画像
重要な原則:preloadは1-2枚の画像に限定すること
複数の画像をpreloadすると、ブラウザの優先順位制御が混乱し、逆にLCPが悪化する可能性があります。
参考記事:rel=preloadとは
実装方法1:基本的なpreload
最もシンプルなpreloadの実装です。
<head>
<!-- その他のメタタグなど -->
<!-- LCP画像のプリロード -->
<link rel="preload" as="image" href="/images/hero.jpg" fetchpriority="high">
</head>
<body>
<!-- ページ本文 -->
<img src="/images/hero.jpg" alt="ヒーロー画像" width="1200" height="600">
</body>ポイント
rel="preload"で「事前読み込み」を指定as="image"でリソースタイプを明示fetchpriority="high"で最優先ダウンロードを指示
実装方法2:レスポンシブ画像のpreload
モバイルとPCで異なる画像を使用している場合の実装です。
<head>
<!-- モバイル向け画像(640px幅以下) -->
<link rel="preload" as="image"
href="/images/hero-mobile.jpg"
imagesrcset="/images/hero-mobile.jpg 640w,
/images/hero-tablet.jpg 1024w,
/images/hero-desktop.jpg 1920w"
imagesizes="100vw"
fetchpriority="high">
</head>
<body>
<img srcset="/images/hero-mobile.jpg 640w,
/images/hero-tablet.jpg 1024w,
/images/hero-desktop.jpg 1920w"
sizes="100vw"
src="/images/hero-desktop.jpg"
alt="ヒーロー画像"
width="1920"
height="800">
</body>重要: imagesrcsetとimagesizesの値は、本文の<img>タグのsrcsetとsizesと完全に一致させる必要があります。
実装方法3:次世代フォーマット(WebP/AVIF)のpreload
WebPやAVIFを使用している場合の実装です。
<head>
<!-- WebP画像のプリロード -->
<link rel="preload" as="image"
href="/images/hero.webp"
type="image/webp"
fetchpriority="high">
<!-- フォールバック用のJPEGもpreload(WebP非対応ブラウザ用) -->
<link rel="preload" as="image"
href="/images/hero.jpg"
fetchpriority="high">
</head>
<body>
<picture>
<source type="image/webp" srcset="/images/hero.webp">
<img src="/images/hero.jpg" alt="ヒーロー画像" width="1200" height="600">
</picture>
</body>
実装方法4:WordPressでのpreload実装
WordPressのfunctions.phpに以下のコードを追加します。
<?php
// LCP画像をプリロードする関数
function add_lcp_image_preload() {
// トップページの場合のみ実行
if (is_front_page()) {
$hero_image_url = get_template_directory_uri() . '/images/hero.jpg';
$hero_webp_url = get_template_directory_uri() . '/images/hero.webp';
// WebP画像のプリロード
echo '<link rel="preload" as="image" href="' . esc_url($hero_webp_url) . '" type="image/webp" fetchpriority="high">' . "\n";
// フォールバック用JPEG
echo '<link rel="preload" as="image" href="' . esc_url($hero_image_url) . '" fetchpriority="high">' . "\n";
}
// 記事ページの場合:アイキャッチ画像をプリロード
if (is_single() && has_post_thumbnail()) {
$thumbnail_id = get_post_thumbnail_id();
$thumbnail_url = wp_get_attachment_image_src($thumbnail_id, 'full')[0];
echo '<link rel="preload" as="image" href="' . esc_url($thumbnail_url) . '" fetchpriority="high">' . "\n";
}
}
// wp_headアクションの最初(優先度1)で実行
add_action('wp_head', 'add_lcp_image_preload', 1);
?>このコードの特徴
- トップページと記事ページで自動的に切り替え
- アイキャッチ画像を動的に取得してプリロード
- 優先度1で最速実行
実装方法5:CSS背景画像のpreload
CSS background-image で設定された画像は、HTMLパース時に発見されないため、preloadが特に重要です。
<head>
<!-- CSS背景画像のプリロード -->
<link rel="preload" as="image" href="/images/hero-background.jpg" fetchpriority="high">
<style>
.hero-section {
background-image: url('/images/hero-background.jpg');
background-size: cover;
background-position: center;
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<div class="hero-section">
<!-- コンテンツ -->
</div>
</body>より良い方法:CSS背景画像をHTMLのimgタグに変更する
CSS背景画像はLCP対策が難しいため、可能であれば<img>タグに変更することを強く推奨します。
よくある失敗パターンと対策
失敗1:複数の画像をpreloadしてしまう
<!-- 悪い例:5枚もpreloadしている -->
<link rel="preload" as="image" href="hero1.jpg">
<link rel="preload" as="image" href="hero2.jpg">
<link rel="preload" as="image" href="hero3.jpg">
<link rel="preload" as="image" href="logo.png">
<link rel="preload" as="image" href="icon.svg">正しい実装:LCP要素のみpreload
<!-- 良い例:LCP画像1枚のみ -->
<link rel="preload" as="image" href="hero.jpg" fetchpriority="high">
失敗2:LCP対象外の画像をpreloadしている
PageSpeed Insightsで特定したLCP要素以外をpreloadしても効果はありません。
必ずLCP要素を正確に特定してから実装してください。
失敗3:preloadとlazy loadを併用している
<!-- 矛盾している -->
<link rel="preload" as="image" href="hero.jpg">
<img src="hero.jpg" loading="lazy"> <!-- lazy loadで遅延される -->LCP画像は必ずloading="eager"(デフォルト)を使用してください。
【優先度高】fetchpriority="high"の設定
項目 | 内容 |
|---|---|
期待効果 | LCP 0.2〜0.8秒 改善 |
難易度 | 初級 |
実装時間 | 5分 〜 15分 |
fetchpriority="high"とは
fetchpriorityは、ブラウザに対して「このリソースを優先的にダウンロードしてください」と明示的に伝えるHTML属性です。
2022年にChromeで正式にサポートされ、現在はほぼすべてのモダンブラウザで利用可能です。
設定可能な値
値 | 意味 | 使用場面 |
|---|---|---|
high | 高優先度 | LCP画像、ファーストビューの重要画像 |
low | 低優先度 | ファーストビュー外の画像 |
auto | 自動(デフォルト) | ブラウザの判断に任せる |
Fetch Priority APIの仕組み
ブラウザは通常、以下のような優先順位でリソースをダウンロードします。
デフォルトの優先順位(高→低)
- HTML
- CSS
- フォント
- スクリプト(async/defer)
- 画像(低優先度)
画像は優先度が低いため、他のリソースのダウンロードが完了するまで待たされることがあります。
fetchpriority="high"を使うと
- HTML
- CSS
- 指定した画像(高優先度に昇格)
- フォント
- スクリプト
- その他の画像
これにより、LCP画像が他のリソースよりも早くダウンロードされ、LCPが改善します。
実装方法1:基本的な使い方
LCP画像の<img>タグにfetchpriority="high"を追加するだけです。
<!-- LCP画像 -->
<img src="hero.jpg"
alt="ヒーロー画像"
width="1200"
height="600"
fetchpriority="high"
loading="eager">
<!-- 通常の画像(ファーストビュー外) -->
<img src="article-image.jpg"
alt="記事内画像"
loading="lazy">fetchpriority="high"とloading="eager"を併用するのがベストプラクティスです。
実装方法2:preloadとの併用
preloadと組み合わせることで、さらに強力な効果が得られます。
<head>
<!-- preload + fetchpriority="high" -->
<link rel="preload"
as="image"
href="hero.jpg"
fetchpriority="high">
</head>
<body>
<!-- 本文でも fetchpriority="high" を指定 -->
<img src="hero.jpg"
alt="ヒーロー画像"
width="1200"
height="600"
fetchpriority="high">
</body>実装方法3:picture要素での使用
次世代フォーマット(WebP/AVIF)を使用している場合の実装です。
<picture>
<source type="image/avif" srcset="hero.avif">
<source type="image/webp" srcset="hero.webp">
<img src="hero.jpg"
alt="ヒーロー画像"
width="1200"
height="600"
fetchpriority="high"
loading="eager">
</picture>fetchpriority属性は<img>タグにのみ設定します。
<source>タグには不要です。
実装方法4:CSS背景画像の場合
CSS背景画像の場合、fetchpriorityは直接使えませんが、preloadと組み合わせることで同様の効果が得られます。
<head>
<!-- preloadでfetchpriority="high"を指定 -->
<link rel="preload"
as="image"
href="hero-background.jpg"
fetchpriority="high">
</head>WordPressでの実装
方法1:テーマファイルを直接編集
header.phpやsingle.phpなどのテンプレートファイルを編集します。
<?php if (has_post_thumbnail()): ?>
<img src="<?php echo get_the_post_thumbnail_url(null, 'full'); ?>"
alt="<?php the_title_attribute(); ?>"
width="1200"
height="600"
fetchpriority="high"
loading="eager">
<?php endif; ?>方法2:フィルターフックで自動追加
functions.phpに以下のコードを追加すると、アイキャッチ画像に自動的にfetchpriority="high"が追加されます。
<?php
// アイキャッチ画像にfetchpriority="high"を自動追加
function add_fetchpriority_to_thumbnail($html, $post_id, $post_thumbnail_id, $size, $attr) {
// 記事ページの場合のみ追加
if (is_single()) {
// すでに属性がある場合は置換、ない場合は追加
if (strpos($html, 'fetchpriority') === false) {
$html = str_replace('<img', '<img fetchpriority="high" loading="eager"', $html);
}
}
return $html;
}
add_filter('post_thumbnail_html', 'add_fetchpriority_to_thumbnail', 10, 5);
?>Next.js/Nuxt.jsでの実装
Next.jsの場合
import Image from 'next/image'
export default function HeroSection() {
return (
<Image
src="/images/hero.jpg"
width={1200}
height={600}
alt="ヒーロー画像"
priority // これがfetchpriority="high"と同等
/>
)
}Next.jsのpriorityプロップは、内部的にfetchpriority="high"とpreloadの両方を自動設定します。
Nuxt.jsの場合
<template>
<nuxt-img
src="/images/hero.jpg"
width="1200"
height="600"
alt="ヒーロー画像"
loading="eager"
fetchpriority="high"
/>
</template>preloadとfetchpriorityの違い
よく混同されますが、この2つは異なる役割を持ちます。
属性 | 役割 | 使用場所 |
|---|---|---|
preload | リソースの発見を早める |
|
fetchpriority | ダウンロードの優先順位を上げる |
|
ベストプラクティス
- HTML内の
<img>タグで直接記述されている画像 →fetchpriority="high"のみ - CSS背景画像やJavaScriptで動的に挿入される画像 →
preload + fetchpriority="high"の両方
よくある失敗パターン
失敗1:複数の画像にfetchpriority="high"を設定
<!-- 悪い例 -->
<img src="hero1.jpg" fetchpriority="high">
<img src="hero2.jpg" fetchpriority="high">
<img src="hero3.jpg" fetchpriority="high">複数の画像を「高優先度」にすると、結果的にどれも優先されない状態になります。
fetchpriority="high"は1枚のLCP画像のみに使用してください。
失敗2:ファーストビュー外の画像にfetchpriority="high"を設定
ファーストビュー外の画像に高優先度を設定しても、LCP改善には寄与しません。
逆に、本当に必要なリソースのダウンロードを妨げる可能性があります。
正しい使い方
- LCP画像(1枚):
fetchpriority="high" - ファーストビュー内のその他の画像:デフォルト(auto)
- ファーストビュー外の画像:
loading="lazy"(fetchpriorityは不要)
【優先度高】画像サイズの最適化(適切なリサイズ)
項目 | 内容 |
|---|---|
期待効果 | ファイルサイズ 40〜80% 削減 |
難易度 | 中級 |
実装時間 | 2〜4時間(初回設定) |
なぜ画像サイズの最適化が重要なのか
多くのWebサイトで、実際の表示サイズの2倍以上の大きさの画像が使用されています。
例
- 実際の表示サイズ:600px幅
- 使用されている画像:2400px幅
- 無駄なデータ量:約75%
これは、モバイルユーザーが貴重な通信容量を使って、絶対に見えない余分なピクセルをダウンロードしていることを意味します。
デバイス別の適切な画像サイズ
現代のWebサイトでは、デバイスの画面サイズに応じて異なるサイズの画像を配信する必要があります。
デバイス | 画面幅 | 推奨画像幅 | Retina対応(2x) |
|---|---|---|---|
スマートフォン(小) | 320-375px | 640-750px | 1280-1500px |
スマートフォン(大) | 390-430px | 780-860px | 1560-1720px |
タブレット | 768-1024px | 1024px | 2048px |
デスクトップ(標準) | 1366-1920px | 1920px | 3840px |
デスクトップ(4K) | 2560px+ | 2560px | 5120px |
推奨戦略
- モバイル:640px、750px
- タブレット:1024px
- デスクトップ:1920px
- 最大サイズ:2560px(4Kディスプレイ用)
これ以上大きなサイズは、ほとんどの場合不要です。
srcset/sizes属性を使ったレスポンシブ画像の実装
HTML5のsrcsetとsizes属性を使うと、ブラウザが自動的に最適なサイズの画像を選択してくれます。
基本構文
<img srcset="画像URL 画像幅w, 画像URL 画像幅w, ..."
sizes="(メディアクエリ) 表示幅, ... デフォルト表示幅"
src="フォールバック画像"
alt="説明">参考記事:レスポンシブ画像とは
実装例1:基本的なレスポンシブ画像
<img srcset="/images/hero-640.jpg 640w,
/images/hero-1024.jpg 1024w,
/images/hero-1920.jpg 1920w"
sizes="100vw"
src="/images/hero-1920.jpg"
alt="ヒーロー画像"
width="1920"
height="800"
fetchpriority="high">このコードの動作
- 画面幅が640px以下 →
hero-640.jpgを使用 - 画面幅が641-1024px →
hero-1024.jpgを使用 - 画面幅が1025px以上 →
hero-1920.jpgを使用
sizes="100vw"は「画像はビューポート幅の100%で表示される」という意味です。
実装例2:メディアクエリを使った高度な制御
画像がビューポート幅の100%ではなく、特定の幅で表示される場合の実装です。
<img srcset="/images/product-400.jpg 400w,
/images/product-800.jpg 800w,
/images/product-1200.jpg 1200w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw"
src="/images/product-1200.jpg"
alt="商品画像"
width="1200"
height="1200">このコードの動作
- 画面幅640px以下:画像は画面幅の100% → 640px画像を使用
- 画面幅641-1024px:画像は画面幅の50% → 800px画像を使用
- 画面幅1025px以上:画像は画面幅の33% → 1200px画像を使用
実装例3:Retina(高解像度)ディスプレイ対応
Retina(2x)ディスプレイでは、通常の2倍のピクセル密度が必要です。
<img srcset="/images/hero-640.jpg 640w,
/images/hero-1280.jpg 1280w,
/images/hero-1920.jpg 1920w,
/images/hero-3840.jpg 3840w"
sizes="(max-width: 640px) 100vw,
(max-width: 1920px) 100vw,
1920px"
src="/images/hero-1920.jpg"
alt="ヒーロー画像"
width="1920"
height="800"
fetchpriority="high">Retinaディスプレイでの動作
- iPhone(375px幅、2xディスプレイ)→ 750px必要 → 1280px画像を使用
- デスクトップ(1920px幅、1xディスプレイ)→ 1920px画像を使用
- Retina Mac(1920px幅、2xディスプレイ)→ 3840px必要 → 3840px画像を使用
実装例4:picture要素での完全制御
デバイスごとに完全に異なる画像(トリミングやアスペクト比が異なる)を使用する場合は、<picture>要素を使います。
<picture>
<!-- モバイル:縦長の画像(9:16) -->
<source media="(max-width: 640px)"
srcset="/images/hero-mobile-640.jpg 640w,
/images/hero-mobile-1280.jpg 1280w"
sizes="100vw">
<!-- タブレット:正方形の画像(1:1) -->
<source media="(max-width: 1024px)"
srcset="/images/hero-tablet-1024.jpg 1024w,
/images/hero-tablet-2048.jpg 2048w"
sizes="100vw">
<!-- デスクトップ:横長の画像(16:9) -->
<source media="(min-width: 1025px)"
srcset="/images/hero-desktop-1920.jpg 1920w,
/images/hero-desktop-3840.jpg 3840w"
sizes="100vw">
<!-- フォールバック -->
<img src="/images/hero-desktop-1920.jpg"
alt="ヒーロー画像"
width="1920"
height="1080"
fetchpriority="high">
</picture>WordPressでの実装
方法1:WordPress標準機能を使う
WordPressは、アップロードした画像を自動的に複数サイズで生成します。これを活用します。
<?php
// アイキャッチ画像をレスポンシブ対応で出力
if (has_post_thumbnail()) {
the_post_thumbnail('full', array(
'sizes' => '(max-width: 640px) 100vw, (max-width: 1024px) 100vw, 1920px',
'fetchpriority' => 'high',
'loading' => 'eager'
));
}
?>方法2:カスタム画像サイズを定義
functions.phpに以下を追加して、必要なサイズを定義します。
<?php
// カスタム画像サイズを追加
function add_custom_image_sizes() {
// LCP用の最適化サイズ
add_image_size('lcp-mobile', 640, 9999, false); // 幅640px、高さ自動
add_image_size('lcp-tablet', 1024, 9999, false); // 幅1024px、高さ自動
add_image_size('lcp-desktop', 1920, 9999, false); // 幅1920px、高さ自動
add_image_size('lcp-retina', 3840, 9999, false); // 幅3840px、高さ自動(Retina用)
}
add_action('after_setup_theme', 'add_custom_image_sizes');
// 既存画像を再生成する場合は「Regenerate Thumbnails」プラグインを使用
?>方法3:完全自動化(Advanced Custom Fieldsを使用)
<?php
// ACFで登録した画像フィールドをレスポンシブ出力
$image_id = get_field('hero_image');
if ($image_id) {
echo wp_get_attachment_image($image_id, 'full', false, array(
'srcset' => wp_get_attachment_image_srcset($image_id, 'full'),
'sizes' => '(max-width: 640px) 100vw, (max-width: 1024px) 100vw, 1920px',
'fetchpriority' => 'high',
'loading' => 'eager'
));
}
?>画像リサイズの自動化ツール
手動でのリサイズは非常に手間がかかるため、自動化ツールの活用を強く推奨します。
1. Cloudinary(クラウドベース・推奨)
<!-- Cloudinaryの自動リサイズURL -->
<img srcset="https://res.cloudinary.com/your-cloud/image/upload/w_640,f_auto,q_auto/hero.jpg 640w,
https://res.cloudinary.com/your-cloud/image/upload/w_1024,f_auto,q_auto/hero.jpg 1024w,
https://res.cloudinary.com/your-cloud/image/upload/w_1920,f_auto,q_auto/hero.jpg 1920w"
sizes="100vw"
src="https://res.cloudinary.com/your-cloud/image/upload/w_1920,f_auto,q_auto/hero.jpg"
alt="ヒーロー画像">URLパラメータの意味
w_640→ 幅640pxにリサイズf_auto→ 最適なフォーマット(WebP/AVIF)を自動選択q_auto→ 最適な品質を自動選択
2. imgix(クラウドベース)
<img srcset="https://your-domain.imgix.net/hero.jpg?w=640&auto=format,compress 640w,
https://your-domain.imgix.net/hero.jpg?w=1024&auto=format,compress 1024w,
https://your-domain.imgix.net/hero.jpg?w=1920&auto=format,compress 1920w"
sizes="100vw"
src="https://your-domain.imgix.net/hero.jpg?w=1920&auto=format,compress"
alt="ヒーロー画像">3. Sharp(Node.js・ローカル処理)
大量の画像を一括処理したい場合は、Sharpを使ったスクリプトが便利です。
// package.json
// npm install sharp
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
// リサイズする幅のリスト
const sizes = [640, 1024, 1920];
// 処理する画像のディレクトリ
const inputDir = './images/original';
const outputDir = './images/optimized';
// ディレクトリ内のすべての画像を処理
fs.readdirSync(inputDir).forEach(filename => {
const inputPath = path.join(inputDir, filename);
const name = path.parse(filename).name;
const ext = path.parse(filename).ext;
sizes.forEach(width => {
sharp(inputPath)
.resize(width, null, { withoutEnlargement: true })
.webp({ quality: 85 })
.toFile(path.join(outputDir, `${name}-${width}.webp`))
.then(() => console.log(`Generated: ${name}-${width}.webp`));
});
});このスクリプトを実行すると、originalフォルダの画像が自動的に640px、1024px、1920pxの3サイズにリサイズされ、WebP形式でoptimizedフォルダに保存されます。
実行方法
node resize-images.jsよくある失敗パターン
失敗1:オリジナルサイズが小さすぎる
元画像が1000px幅しかないのに、1920px用の画像を作ろうとすると、画像が拡大され、画質が劣化します。
対策: オリジナル画像は、必要な最大サイズ(通常は1920px、Retina対応なら3840px)以上で用意してください。
失敗2:sizes属性の値が間違っている
<!-- 悪い例:sizes値が実際のレイアウトと合っていない -->
<img srcset="image-640.jpg 640w, image-1920.jpg 1920w"
sizes="100vw" <!-- 実際は画面の50%幅なのに100vwと指定 -->
style="width: 50%;">sizes属性は、実際のCSSでの表示幅と一致させる必要があります。
正しい例
<img srcset="image-640.jpg 640w, image-1920.jpg 1920w"
sizes="50vw" <!-- 実際の表示幅に合わせる -->
style="width: 50%;">失敗3:width/height属性を省略している
<!-- 悪い例:width/heightがない -->
<img srcset="image-640.jpg 640w, image-1920.jpg 1920w"
sizes="100vw"
src="image-1920.jpg"
alt="画像">width/height属性がないと、CLS(Cumulative Layout Shift)が発生します。
正しい例
<img srcset="image-640.jpg 640w, image-1920.jpg 1920w"
sizes="100vw"
src="image-1920.jpg"
alt="画像"
width="1920"
height="1080">【優先度中】画像圧縮の最適化
項目 | 内容 |
|---|---|
期待効果 | ファイルサイズ 20〜50% 削減 |
難易度 | 初級 |
実装時間 | 1〜2時間 |
圧縮の種類:可逆圧縮と非可逆圧縮
画像圧縮には2種類あります。
圧縮タイプ | 説明 | 圧縮率 | 用途 |
|---|---|---|---|
可逆圧縮(Lossless) | 画質劣化なし | 低い(10-30%削減) | ロゴ、図表、テキスト入り画像 |
非可逆圧縮(Lossy) | 多少の画質劣化 | 高い(50-80%削減) | 写真、ヒーロー画像 |
LCP改善には、非可逆圧縮が効果的です。
適切な品質設定であれば、肉眼ではほとんど劣化が分かりません。
画像タイプ別の推奨圧縮設定
画像タイプ | フォーマット | 品質設定 | 圧縮タイプ | 理由 |
|---|---|---|---|---|
ヒーロー画像(LCP対象) | WebP | 80-85 | 非可逆 | 高品質を維持しつつファイルサイズ削減 |
商品画像 | WebP | 75-80 | 非可逆 | 品質とサイズのバランス重視 |
記事内の写真 | WebP | 70-75 | 非可逆 | 軽量性を優先 |
サムネイル画像 | WebP | 60-70 | 非可逆 | 小さいので低品質でも問題なし |
ロゴ・アイコン | PNG/SVG | 100 | 可逆 | 鮮明さが最重要 |
図表・グラフ | PNG/WebP | 90-100 | 可逆 | テキストやラインの鮮明さが必要 |
LCP対象のヒーロー画像は品質80以上を推奨します。
ファーストインプレッションを決める画像なので、品質を犠牲にしすぎないことが重要です。
圧縮ツールの比較
オンラインツール(手軽・無料)
ツール名 | 圧縮率 | 使いやすさ | バッチ処理 | 特徴 |
|---|---|---|---|---|
TinyPNG | ★★ | ★★★ | ○(20枚まで) | PNG/JPEGに特化、WebP出力可能 |
Squoosh | ★★★ | ★★ | × | Google製、WebP/AVIF対応、詳細設定可 |
Compressor.io | ★★ | ★★★ | × | シンプルで分かりやすい |
ImageOptim Online | ★★ | ★★ | ○ | メタデータ削除に強い |
WordPressプラグイン(自動化)
プラグイン名 | 圧縮方式 | WebP対応 | 無料枠 |
|---|---|---|---|
Imagify | クラウド | ○ | 月20MB |
ShortPixel | クラウド | ○ | 月100枚 |
EWWW Image Optimizer | サーバー/クラウド | ○ | 無制限(基本機能) |
Smush | クラウド | △(Pro版のみ) | 月50枚 |
実際の改善例
項目 | 圧縮前 | 圧縮後 | 改善率 |
|---|---|---|---|
ファイルサイズ | 2.8MB | 680KB | 76%削減 |
LCP時間 | 3.8秒 | 2.1秒 | 45%改善 |
PageSpeed Score | 52点 | 78点 | +26点 |
よくある失敗パターン
失敗1:品質設定が低すぎる
# 悪い例:品質50は低すぎる
cwebp -q 50 hero.jpg -o hero.webp品質50以下では、特にヒーロー画像の場合、明らかに画質劣化が見えます。
正しい設定
# 良い例:品質80-85が最適
cwebp -q 85 hero.jpg -o hero.webp失敗2:元ファイルをバックアップせずに上書き
ImageOptimなどのツールは元ファイルを上書きします。万が一、圧縮後の画像に問題があった場合、元に戻せなくなります。
対策: 必ず元ファイルのバックアップを取ってから圧縮を実行してください。
失敗3:すでに圧縮済みの画像を再度圧縮
すでに最適化された画像を再度圧縮しても、ファイルサイズはほとんど変わらず、画質だけが劣化します。
確認方法
- ファイルのメタデータを確認
- 圧縮ツールで「削減率0%」と表示される場合は、すでに最適化済み
【優先度中】遅延読み込み(Lazy Load)の正しい設定
項目 | 内容 |
|---|---|
期待効果 | 初期読み込み量の削減 |
難易度 | 初級 |
実装時間 | 30分 〜 1時間 |
最重要の注意点:LCP画像にlazy loadは絶対にNG!
まず最初に、最も重要な原則を理解してください。
LCP画像にloading="lazy"を設定すると、LCPが大幅に悪化します。
理由
loading="lazy"は「ビューポートに近づいたら読み込む」という指示- LCP画像は最初から見えている必要がある
- 遅延読み込みによって、LCP画像の読み込みが数百ミリ秒遅れる
実験データ
- LCP画像に
loading="eager":LCP 2.1秒 - LCP画像に
loading="lazy":LCP 3.8秒(1.7秒も悪化!)
lazy loadの正しい使い方
使うべき画像
- ファーストビュー外の画像(スクロールしないと見えない画像)
- 記事本文内の画像
- ギャラリーの画像
- フッターの画像
使ってはいけない画像
- LCP画像(ヒーロー画像、メイン商品画像など)
- ファーストビュー内のすべての画像
- ロゴ
- ナビゲーション内のアイコン
参考記事:遅延読み込みとは
実装方法1:HTML標準のlazy load
HTML5のloading属性を使うのが最もシンプルです。
<!-- LCP画像:loading="eager"(デフォルト) -->
<img src="hero.jpg"
alt="ヒーロー画像"
width="1200"
height="600"
loading="eager"
fetchpriority="high">
<!-- ファーストビュー外の画像:loading="lazy" -->
<img src="article-image-1.jpg"
alt="記事内画像1"
width="800"
height="600"
loading="lazy">
<img src="article-image-2.jpg"
alt="記事内画像2"
width="800"
height="600"
loading="lazy">実装方法2:WordPressでの設定
WordPress 5.5以降では、デフォルトですべての画像に自動的にloading="lazy"が追加されます。
問題点: LCP画像にも自動的にloading="lazy"が追加されてしまうため、LCP画像は手動で除外する必要があります。
解決策1:テーマファイルで明示的に指定
<?php
// アイキャッチ画像(LCP対象)
if (has_post_thumbnail()) {
the_post_thumbnail('full', array(
'loading' => 'eager', // lazy loadを無効化
'fetchpriority' => 'high'
));
}
?>解決策2:フィルターフックで特定画像を除外
functions.phpに以下のコードを追加します。
<?php
// 特定の画像からlazy loadを除外
function exclude_lazy_load_for_lcp_images($attr, $attachment, $size) {
// 記事ページのアイキャッチ画像の場合
if (is_single() && get_post_thumbnail_id() === $attachment->ID) {
$attr['loading'] = 'eager';
$attr['fetchpriority'] = 'high';
}
// トップページの特定画像の場合
if (is_front_page()) {
// ここに条件を追加
}
return $attr;
}
add_filter('wp_get_attachment_image_attributes', 'exclude_lazy_load_for_lcp_images', 10, 3);
?>解決策3:プラグインで制御
「a3 Lazy Load」などのプラグインを使用すると、除外設定が簡単にできます。
- プラグインをインストール・有効化
- 設定画面で「Exclude Images」セクションを開く
- LCP画像のクラス名やIDを追加
例:
.hero-image
#main-visual
.post-thumbnail実装方法3:Intersection Observer API(カスタム実装)
より細かい制御が必要な場合は、Intersection Observer APIを使って自作できます。
<img data-src="article-image.jpg"
alt="記事内画像"
width="800"
height="600"
class="lazy">
<script>
document.addEventListener("DOMContentLoaded", function() {
const lazyImages = document.querySelectorAll('img.lazy');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
}, {
rootMargin: '200px' // ビューポートの200px手前から読み込み開始
});
lazyImages.forEach(img => {
imageObserver.observe(img);
});
});
</script>rootMarginの調整
0px:画像がビューポートに入った瞬間に読み込み200px:ビューポートの200px手前で読み込み開始(推奨)500px:ビューポートの500px手前で読み込み開始(高速回線向け)
よくある失敗パターン
失敗1:すべての画像にlazy loadを適用
<!-- 悪い例:LCP画像にもlazy load -->
<img src="hero.jpg" loading="lazy">
<img src="article-1.jpg" loading="lazy">
<img src="article-2.jpg" loading="lazy">正しい例
<!-- 良い例:LCP画像は除外 -->
<img src="hero.jpg" loading="eager" fetchpriority="high">
<img src="article-1.jpg" loading="lazy">
<img src="article-2.jpg" loading="lazy">失敗2:ファーストビュー内の複数画像にlazy loadを適用
ナビゲーションのロゴ、ヒーロー画像の下のCTAボタンなど、ファーストビュー内のすべての画像は即座に読み込むべきです。
失敗3:width/height属性がない状態でlazy loadを使用
<!-- 悪い例:width/heightがない -->
<img data-src="image.jpg" loading="lazy">width/height属性がないと、画像読み込み前にレイアウトシフト(CLS)が発生します。
正しい例
<img data-src="image.jpg" loading="lazy" width="800" height="600">【優先度中】CDNの活用
項目 | 内容 |
|---|---|
期待効果 | TTFB 100〜500ms 改善グローバル配信の高速化 |
難易度 | 中級 |
実装時間 | 2〜4時間(初回設定) |
CDN(コンテンツデリバリネットワーク)とは
CDNは、世界中に配置されたサーバーネットワークを使って、ユーザーに最も近い場所からコンテンツを配信する仕組みです。
参考記事:CDNとは
CDNがLCP改善に効く理由
CDNは、以下の2つの要素でLCPを改善します。
- TTFB(Time to First Byte)の短縮
- エッジサーバーはユーザーの近くにあるため、HTMLの配信が高速
- 改善効果:100-300ms
- 画像配信の高速化
- 画像もエッジサーバーから配信されるため、ダウンロード時間が短縮
- 改善効果:200-500ms
合計改善効果:300-800ms
推奨CDNサービス比較
サービス | 画像最適化 | 自動WebP変換 | 価格帯 | 特徴 |
|---|---|---|---|---|
Cloudflare | ○(有料) | ○(有料) | 無料~月20ドル | 無料プランでも高性能、日本語対応 |
Cloudinary | ◎ | ◎ | 無料枠25GB/月 | 画像最適化に特化、URLで変換可能 |
imgix | ◎ | ◎ | 従量課金 | 高度な画像処理、リアルタイム変換 |
Fastly | ○ | △ | 企業向け | 高速だが高価、大規模サイト向け |
Bunny CDN | ○ | ○ | 月1ドル~ | コスパ最高、シンプルな設定 |
KeyCDN | ○ | ○ | 従量課金 | 低価格、ヨーロッパに強い |
実装方法1:Cloudflareの導入(最も簡単・推奨)
Cloudflareは無料プランでも高性能で、設定も簡単です。
設定手順:
- Cloudflareアカウント作成
- Cloudflareでアカウント登録
- サイトを追加
- ダッシュボードで「サイトを追加」をクリック
- ドメイン名を入力
- DNSレコードのインポート
- Cloudflareが自動的に既存のDNSレコードをスキャン
- 確認して「続行」
- ネームサーバーの変更
- Cloudflareが提供する2つのネームサーバーをメモ
- ドメイン登録業者(お名前.comなど)の管理画面でネームサーバーを変更
- 例:
古いネームサーバー:ns1.example.com 新しいネームサーバー:dana.ns.cloudflare.com walt.ns.cloudflare.com
- DNS変更の反映を待つ
- 通常、数分~24時間で完了
- 画像最適化の有効化(有料プラン)
- 「速度」→「最適化」→「画像最適化」を有効化
- WebP自動変換も有効化
無料プランでできること
- CDN配信(エッジキャッシング)
- SSL/TLS証明書の自動発行
- DDoS保護
有料プラン(月20ドル~)でできること
- 画像の自動WebP変換
- 画像圧縮
- Lazy Load自動適用
実装方法2:Cloudinary(画像最適化特化・推奨)
Cloudinaryは画像最適化に特化したCDNで、URLパラメータで自由に画像を変換できます。
設定手順
- Cloudinaryアカウント作成
- Cloudinaryで無料アカウント登録
- 画像をアップロード
- ダッシュボードから「Media Library」→「Upload」
- または、既存のサーバーから自動同期設定
- 画像URLを取得
- アップロードした画像のURLが自動生成される
- 基本形式:
https://res.cloudinary.com/your-cloud-name/image/upload/v1234567890/sample.jpg
- URLパラメータで最適化
<!-- 元の画像 -->
https://res.cloudinary.com/demo/image/upload/sample.jpg
<!-- 幅800px、自動フォーマット、自動品質 -->
https://res.cloudinary.com/demo/image/upload/w_800,f_auto,q_auto/sample.jpg
<!-- レスポンシブ対応 -->
<img srcset="https://res.cloudinary.com/demo/image/upload/w_640,f_auto,q_auto/sample.jpg 640w,
https://res.cloudinary.com/demo/image/upload/w_1024,f_auto,q_auto/sample.jpg 1024w,
https://res.cloudinary.com/demo/image/upload/w_1920,f_auto,q_auto/sample.jpg 1920w"
sizes="100vw"
src="https://res.cloudinary.com/demo/image/upload/w_1920,f_auto,q_auto/sample.jpg"
alt="サンプル画像">主要なURLパラメータ
パラメータ | 説明 | 例 |
|---|---|---|
| 幅800pxにリサイズ |
|
| 高さ600pxにリサイズ |
|
| 最適なフォーマット自動選択(WebP/AVIF) | - |
| 最適な品質自動選択 |
|
| 指定サイズに切り抜き |
|
| 顔を中心に切り抜き |
|
WordPressプラグインで自動化
「Cloudinary」公式プラグインをインストールすると、WordPressにアップロードした画像が自動的にCloudinaryにアップロードされます。
- プラグイン「Cloudinary」をインストール
- Cloudinaryのクラウド名、APIキーを入力
- 「Auto sync」を有効化
これで、既存の画像も新規画像もすべて自動的にCloudinaryから配信されます。
実装方法3:Bunny CDN(コスパ重視)
Bunny CDNは、月1ドルから使える低価格CDNです。
設定手順
- Bunny CDNアカウント作成
- Bunny CDNでアカウント登録
- Pull Zoneの作成
- 「CDN」→「Add Pull Zone」
- あなたのサイトのオリジンURL(例:https://example.com)を入力
- Zone名を決定(例:example-cdn)
- CNAMEレコードを設定
- Bunnyが提供するCDN URL:
example-cdn.b-cdn.net - ドメインのDNS設定で、サブドメイン(例:cdn.example.com)にCNAMEレコードを追加
cdn.example.com CNAME example-cdn.b-cdn.net - Bunnyが提供するCDN URL:
- WordPressでCDN URLを設定
- プラグイン「WP Super Cache」または「W3 Total Cache」をインストール
- CDN設定で
https://cdn.example.comを指定
WordPressでのCDN設定
プラグインを使わない方法(functions.php)
<?php
// すべての画像URLをCDNに書き換え
function rewrite_image_urls_to_cdn($content) {
$cdn_url = 'https://cdn.example.com'; // あなたのCDN URL
$site_url = get_site_url();
// 画像URLをCDN URLに置換
$content = str_replace($site_url . '/wp-content/uploads/', $cdn_url . '/wp-content/uploads/', $content);
return $content;
}
add_filter('the_content', 'rewrite_image_urls_to_cdn');
add_filter('post_thumbnail_html', 'rewrite_image_urls_to_cdn');
?>プラグインを使う方法(推奨)
「CDN Enabler」プラグインが最もシンプルです。
- プラグイン「CDN Enabler」をインストール・有効化
- 「設定」→「CDN Enabler」
- 「CDN URL」にあなたのCDN URLを入力(例:
https://cdn.example.com) - 「変更を保存」
これで、すべての画像が自動的にCDNから配信されます。
CDN導入前後の効果測定
測定方法
- CDN導入前のPageSpeed Insightsスコアを記録
- CDNを設定
- 24時間待つ(キャッシュが完全に分散するまで)
- 再度PageSpeed Insightsでテスト
実際の改善例
項目 | CDN導入前 | CDN導入後 | 改善 |
|---|---|---|---|
TTFB | 850ms | 320ms | 62%改善 |
LCP | 3.2秒 | 2.1秒 | 34%改善 |
PageSpeed Score | 68点 | 87点 | +19点 |
特に効果が高いケース
- 海外からのアクセスが多いサイト
- 画像が多いメディアサイト・ECサイト
- 共有サーバーを使用しているサイト
よくある失敗パターン
失敗1:CDN設定後、キャッシュをパージしていない
CDNは一度配信した画像をキャッシュするため、元の画像を更新しても古い画像が表示され続けます。
対策: 画像を更新したら、必ずCDNのキャッシュをパージ(削除)してください。
Cloudflareの場合
- ダッシュボード →「キャッシング」→「キャッシュのパージ」→「すべてをパージ」
失敗2:SSL証明書の設定ミス
HTTPとHTTPSが混在すると、ブラウザが「混在コンテンツ」として画像をブロックする場合があります。
対策: CDN URLは必ずhttps://から始まるようにしてください。
失敗3:オリジンサーバーのキャッシュ設定が不適切
CDNが効率的にキャッシュするには、オリジンサーバーが適切なCache-Controlヘッダーを返す必要があります。
.htaccessでの設定例
<IfModule mod_expires.c>
ExpiresActive On
# 画像は1年間キャッシュ
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/avif "access plus 1 year"
</IfModule>【優先度中】CSS背景画像のLCP対策
項目 | 内容 |
|---|---|
期待効果 | LCP 0.5〜1.5秒 改善 |
難易度 | 中級 |
実装時間 | 1〜2時間 |
なぜCSS背景画像はLCPに不利なのか
CSS background-imageで設定された画像は、以下の理由でLCPを悪化させます。
読み込みタイミングの遅延:
- HTMLのダウンロード
- HTMLのパース
- CSSのダウンロード
- CSSのパース(ここで初めて背景画像が発見される)
- 背景画像のダウンロード開始
一方、HTML内の<img>タグは、HTMLパース中に発見されるため、より早くダウンロードが開始されます。
遅延の実測値
<img>タグ:HTMLダウンロード開始から約200ms後にダウンロード開始- CSS背景画像:HTMLダウンロード開始から約800ms後にダウンロード開始
- 差:約600ms
この600msの遅延が、LCPを直接悪化させます。
解決策1:preloadで優先読み込み(応急処置)
最も簡単な方法は、<link rel="preload">を使って背景画像を優先読み込みすることです。
<head>
<!-- CSS背景画像をプリロード -->
<link rel="preload"
as="image"
href="/images/hero-background.jpg"
fetchpriority="high">
<style>
.hero-section {
background-image: url('/images/hero-background.jpg');
background-size: cover;
background-position: center;
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<div class="hero-section">
<h1>ヒーローセクション</h1>
</div>
</body>効果
- preloadなし:LCP 3.5秒
- preloadあり:LCP 2.8秒
- 改善:0.7秒
ただし、これは応急処置です。根本的な解決には、次の方法が推奨されます。
解決策2:HTMLのimgタグに変更(推奨)
CSS背景画像を<img>タグに置き換え、CSSで絶対配置することで、LCPを大幅に改善できます。
Before:CSS背景画像
<style>
.hero-section {
background-image: url('/images/hero-bg.jpg');
background-size: cover;
background-position: center;
width: 100%;
height: 600px;
}
</style>
<div class="hero-section">
<h1>ヒーローセクション</h1>
</div>After:imgタグ + 絶対配置
<style>
.hero-section {
position: relative;
width: 100%;
height: 600px;
overflow: hidden;
}
.hero-bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
z-index: -1; /* 背景として配置 */
}
.hero-content {
position: relative;
z-index: 1; /* コンテンツを前面に */
}
</style>
<div class="hero-section">
<!-- 背景画像をimgタグで -->
<img src="/images/hero-bg.jpg"
alt="ヒーロー背景画像"
class="hero-bg-image"
width="1920"
height="600"
fetchpriority="high"
loading="eager">
<!-- コンテンツ -->
<div class="hero-content">
<h1>ヒーローセクション</h1>
</div>
</div>この方法の利点
- HTMLパース時に画像が発見される → 早期ダウンロード開始
fetchpriority="high"が使えるpreloadが不要- SEO的にも有利(alt属性を設定できる)
改善効果
- CSS背景画像(preloadあり):LCP 2.8秒
- imgタグに変更:LCP 2.1秒
- 改善:0.7秒
解決策3:picture要素でレスポンシブ対応
モバイルとPCで異なる背景画像を使いたい場合は、<picture>要素を使います。
<style>
.hero-section {
position: relative;
width: 100%;
height: 600px;
overflow: hidden;
}
.hero-bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
}
.hero-content {
position: relative;
z-index: 1;
}
</style>
<div class="hero-section">
<!-- レスポンシブ背景画像 -->
<picture>
<!-- モバイル:縦長画像 -->
<source media="(max-width: 640px)"
srcset="/images/hero-mobile.jpg"
width="640"
height="960">
<!-- タブレット:正方形画像 -->
<source media="(max-width: 1024px)"
srcset="/images/hero-tablet.jpg"
width="1024"
height="1024">
<!-- デスクトップ:横長画像 -->
<img src="/images/hero-desktop.jpg"
alt="ヒーロー背景画像"
class="hero-bg-image"
width="1920"
height="1080"
fetchpriority="high"
loading="eager">
</picture>
<!-- コンテンツ -->
<div class="hero-content">
<h1>ヒーローセクション</h1>
</div>
</div>実装例:WordPressテーマでの実装
カスタムフィールド(ACF)を使った実装
<style>
.hero-section {
position: relative;
width: 100%;
height: 600px;
overflow: hidden;
}
.hero-bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
}
.hero-content {
position: relative;
z-index: 1;
padding: 100px 20px;
}
</style>
<div class="hero-section">
<?php
$hero_image_id = get_field('hero_background_image');
if ($hero_image_id) {
echo wp_get_attachment_image($hero_image_id, 'full', false, array(
'class' => 'hero-bg-image',
'fetchpriority' => 'high',
'loading' => 'eager'
));
}
?>
<div class="hero-content">
<h1><?php the_field('hero_title'); ?></h1>
<p><?php the_field('hero_description'); ?></p>
</div>
</div>よくある失敗パターン
失敗1:preloadのURLがCSSのURLと一致していない
<!-- ❌ 悪い例:URLが一致していない -->
<link rel="preload" href="/images/hero.jpg">
<style>
.hero { background-image: url('/img/hero.jpg'); } <!-- パスが違う -->
</style>preloadとCSSのURLが1文字でも違うと、ブラウザは別の画像と認識し、2回ダウンロードしてしまいます。
正しい例
<link rel="preload" href="/images/hero.jpg">
<style>
.hero { background-image: url('/images/hero.jpg'); } <!-- 完全一致 -->
</style>失敗2:imgタグに変更したが、z-indexを設定していない
<!-- 悪い例:背景画像がコンテンツの前面に来てしまう -->
<div class="hero">
<img src="hero-bg.jpg" class="hero-bg">
<h1>見えない!</h1> <!-- 画像の下に隠れる -->
</div>正しい例
.hero {
position: relative;
}
.hero-bg {
position: absolute;
z-index: -1; /* 背景として配置 */
}
.hero h1 {
position: relative;
z-index: 1; /* コンテンツを前面に */
}失敗3:object-fitが効かないブラウザへの対応を忘れている
古いブラウザ(IE11など)ではobject-fitプロパティが効きません。
フォールバック:
.hero-bg-image {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
/* IE11用のフォールバック */
@supports not (object-fit: cover) {
display: none; /* または別の対応 */
}
}【優先度低】サーバー側の圧縮(Gzip/Brotli)
項目 | 内容 |
|---|---|
期待効果 | 転送量 50〜80% 削減全体的な読み込み速度向上 |
難易度 | 中級 |
実装時間 | 30分 〜 1時間 |
Gzip/Brotli圧縮とは
GzipとBrotliは、サーバーからブラウザにデータを転送する際に、データを圧縮する技術です。
動作の流れ
- ブラウザがサーバーにリクエスト(「Gzip/Brotli対応してます」と伝える)
- サーバーがファイルを圧縮
- 圧縮されたデータをブラウザに送信
- ブラウザが解凍して表示
圧縮の対象
種別 | 圧縮対象 | 備考 |
|---|---|---|
HTML | 対象 | テキスト形式で高圧縮可能 |
CSS | 対象 | 同上 |
JavaScript | 対象 | 同上 |
SVG画像 | 対象 | XMLベースで圧縮可 |
JSON | 対象 | テキスト形式で圧縮可 |
XML | 対象 | テキスト形式で圧縮可 |
JPEG / PNG | 対象外 | 既に圧縮済み |
WebP / AVIF | 対象外 | 次世代圧縮形式 |
Gzip vs Brotli
圧縮方式 | 圧縮率 | 圧縮速度 | ブラウザ対応 | 推奨度 |
|---|---|---|---|---|
Gzip | 基準(100%) | 高速 | 100% | ★★★ |
Brotli | より高い(110-120%) | 中速 | 98%+ | ★★★★ |
Brotliが使えればBrotli、使えなければGzipを使用
多くのモダンなサーバーは、両方をサポートし、ブラウザに応じて自動的に選択します。
圧縮効果の実例
HTMLファイルの場合
- 圧縮前:150KB
- Gzip圧縮後:35KB(77%削減)
- Brotli圧縮後:30KB(80%削減)
CSSファイルの場合:
- 圧縮前:200KB
- Gzip圧縮後:40KB(80%削減)
- Brotli圧縮後:35KB(82.5%削減)
実装方法1:Apache(.htaccess)
Apacheサーバーを使用している場合、.htaccessファイルに以下を追加します。
Gzip圧縮の設定
<IfModule mod_deflate.c>
# HTML、CSS、JavaScript、XML、JSONを圧縮
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE image/svg+xml
# 特定のブラウザで圧縮を無効化(古いIE対策)
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
# プロキシが圧縮を正しく処理するための設定
Header append Vary User-Agent
</IfModule>Brotli圧縮の設定(Apache 2.4.26以降)
<IfModule mod_brotli.c>
# Brotli圧縮を有効化
AddOutputFilterByType BROTLI_COMPRESS text/html
AddOutputFilterByType BROTLI_COMPRESS text/css
AddOutputFilterByType BROTLI_COMPRESS text/javascript
AddOutputFilterByType BROTLI_COMPRESS application/javascript
AddOutputFilterByType BROTLI_COMPRESS application/json
AddOutputFilterByType BROTLI_COMPRESS application/xml
AddOutputFilterByType BROTLI_COMPRESS text/xml
AddOutputFilterByType BROTLI_COMPRESS text/plain
AddOutputFilterByType BROTLI_COMPRESS image/svg+xml
</IfModule>両方を併用する場合(推奨)
# Brotli優先、非対応ならGzip
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/css text/javascript application/javascript application/json application/xml text/xml text/plain image/svg+xml
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json application/xml text/xml text/plain image/svg+xml
</IfModule>実装方法2:Nginx
Nginxの設定ファイル(nginx.confまたは/etc/nginx/sites-available/your-site)に以下を追加します。
Gzip圧縮の設定
# Gzip圧縮を有効化
gzip on;
# 圧縮レベル(1-9、推奨は5-6)
gzip_comp_level 6;
# 圧縮する最小ファイルサイズ(これより小さいと圧縮しない)
gzip_min_length 1000;
# 圧縮するMIMEタイプ
gzip_types
text/html
text/css
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
text/xml
text/plain
image/svg+xml;
# プロキシ経由のリクエストも圧縮
gzip_proxied any;
# Vary: Accept-Encodingヘッダーを追加
gzip_vary on;
# IE6では圧縮を無効化(古いブラウザ対策)
gzip_disable "MSIE [1-6]\.";Brotli圧縮の設定
# Brotliモジュールをインストール済みの場合
# https://github.com/google/ngx_brotli
# Brotli圧縮を有効化
brotli on;
# 圧縮レベル(0-11、推奨は4-6)
brotli_comp_level 6;
# 圧縮するMIMEタイプ
brotli_types
text/html
text/css
text/javascript
application/javascript
application/json
application/xml
text/xml
text/plain
image/svg+xml;設定後の反映
# 設定ファイルの構文チェック
sudo nginx -t
# Nginxを再起動
sudo systemctl restart nginx実装方法3:WordPress(プラグイン)
Apacheの設定変更が難しい場合、WordPressプラグインで有効化できます。
推奨プラグイン
プラグイン | Gzip | Brotli | 設定の簡単さ |
|---|---|---|---|
WP Super Cache | ○ | × | ★★★ |
W3 Total Cache | ○ | × | ★★ |
WP Rocket | ○ | × | ★★★ |
LiteSpeed Cache | ○ | ○ | ★★★(LiteSpeedサーバーのみ) |
WP Super Cacheの設定例
- プラグインをインストール・有効化
- 「設定」→「WP Super Cache」→「詳細」タブ
- 「圧縮を有効化」にチェック
- 「ステータスを更新」をクリック
圧縮が有効か確認する方法
方法1:Chrome DevToolsで確認
- Chrome DevToolsを開く(F12)
- 「Network」タブを選択
- ページをリロード
- HTMLファイルをクリック
- 「Headers」タブで「Response Headers」を確認
圧縮されている場合
content-encoding: gzipまたは
content-encoding: br圧縮されていない場合: content-encodingヘッダーがない
方法2:オンラインツールで確認
GiftOfSpeed Gzip Testにアクセスし、あなたのサイトURLを入力します。
結果例
Gzip is enabled
Original size: 150KB
Compressed size: 35KB
Compression ratio: 76.7%方法3:curlコマンドで確認
# Gzip圧縮を確認
curl -H "Accept-Encoding: gzip" -I https://example.com
# Brotli圧縮を確認
curl -H "Accept-Encoding: br" -I https://example.com出力例(圧縮有効)
HTTP/2 200
content-encoding: br
content-type: text/html; charset=UTF-8
...よくある失敗パターン
失敗1:画像ファイルも圧縮しようとしている
<!-- 悪い例:JPEG/PNGも圧縮対象に -->
AddOutputFilterByType DEFLATE image/jpeg
AddOutputFilterByType DEFLATE image/pngJPEG、PNG、WebP、AVIFはすでに圧縮されているため、Gzip/Brotliで再圧縮しても効果はなく、逆にCPUリソースを無駄に消費します。
正しい設定: SVG以外の画像は圧縮しない
失敗2:圧縮レベルが高すぎる
# 悪い例:圧縮レベル9(最大)
gzip_comp_level 9;圧縮レベルを上げすぎると、CPUに負荷がかかり、サーバーの応答が遅くなる場合があります。
推奨設定
- Gzip:レベル5-6
- Brotli:レベル4-6
失敗3:CDNでの圧縮を考慮していない
CloudflareなどのCDNを使用している場合、CDN側で自動的に圧縮されることがあります。
確認方法
- Cloudflareダッシュボード →「速度」→「最適化」→「Auto Minify」を確認
CDN側で圧縮されている場合、オリジンサーバーでの圧縮設定は重複するため、どちらか一方で十分です。
【優先度低】HTTP/2以上の有効化
項目 | 内容 |
|---|---|
期待効果 | 多重化による並列読み込み全体的な速度向上 |
難易度 | 中級 |
実装時間 | 1〜2時間 |
HTTP/2とHTTP/3の違い
プロトコル | リリース | 基盤 | 特徴 | 対応率 |
|---|---|---|---|---|
HTTP/1.1 | 1999年 | TCP | 古い、遅い | 100% |
HTTP/2 | 2015年 | TCP | 多重化、高速 | 98%+ |
HTTP/3 | 2022年 | QUIC(UDP) | さらに高速、接続が安定 | 90%+ |
HTTP/2以上を使用(HTTP/3対応ならなお良い)
参考記事:HTTP/3とは?HTTP/2との違い
HTTP/2が有効か確認する方法
方法1:Chrome DevToolsで確認
- Chrome DevToolsを開く(F12)
- 「Network」タブを選択
- カラムヘッダーを右クリック →「Protocol」にチェック
- ページをリロード
- 「Protocol」列を確認
表示例
- HTTP/1.1:
http/1.1 - HTTP/2:
h2 - HTTP/3:
h3
方法2:オンラインツールで確認
HTTP/2 Testにアクセスし、あなたのサイトURLを入力します。
結果例
HTTP/2 is supported
HTTPS is enabled方法3:curlコマンドで確認
curl -I --http2 https://example.com出力例(HTTP/2有効)
HTTP/2 200
server: nginx
content-type: text/html; charset=UTF-8
...HTTP/2と表示されればHTTP/2が有効です。
LCP改善における画像最適化まとめ
LCP改善の鍵は画像最適化です!まずはWebP形式への変換と、LCP画像へのfetchpriority="high"設定から始めましょう。
これらの施策を1つずつ実践すれば、必ずLCP 2.5秒以下を達成できます。
あなたのサイトも必ず速くなります。
