「画像を全部 loading="lazy" にしたのにPageSpeedのスコアが上がらない」「むしろ表示が遅くなった気がする」——表示速度の改善で最初に踏みやすい罠です。結論から言うと、遅延読み込みは「画面に最初から映らない画像」だけに効く施策で、ファーストビューに入る画像に付けると読み込みの開始そのものが後ろへずれ、LCP(最大コンテンツの描画)は確実に遅くなります。この記事では、LCP要素を機械的に特定する手順、WordPressが自動で付ける lazy を先頭だけ外す方法、fetchpriority の使いどころ、そしてCLSを同時に潰す書き方までを、実案件でやっている順番のまま整理します。
なぜ「全画像lazy」で遅くなるのか
遅延読み込みは、ブラウザに「この画像は今すぐ要らない」と伝える指示です。ブラウザはHTMLを読んだ時点で画像のダウンロードを始めますが、loading="lazy" が付いていると、レイアウトが確定して「その画像がビューポートに近づいた」と判定されるまで開始しません。
ファーストビューのメイン画像は、たいていそのページのLCP要素です。つまりページの表示完了時刻を決めている画像に、わざわざ「後回しでいい」と書いている状態になります。実案件(WordPress・lionmedia系テーマ・記事上部にアイキャッチが出るレイアウト)で、アイキャッチの lazy を外して fetchpriority="high" を付けただけで、モバイル回線相当のスロットリング下でLCPが約2.4秒から約1.6秒まで縮みました。CSSもJSも触っていません。属性2つの差です。
逆に、記事本文の中盤から下にある解説図やギャラリーは lazy が正しく効きます。差が出るのは「最初の1〜2枚をどう扱うか」だけで、そこだけが勝負どころです。
まず自分のサイトのLCP要素を特定する
画像をいじる前に、そのページのLCPが本当に画像なのかを確認します。ヒーローにテキストを大きく置いているデザインだと、LCP要素が h1 や見出しのテキストブロックであることは珍しくありません。その場合は画像を最適化しても数値は動かず、フォント読み込みの方が効きます。
PageSpeed Insightsの「最大コンテンツの描画要素」でも分かりますが、実機やログイン後の画面で確認したいときは、コンソールに次を貼るのが早いです。
// このページのLCP要素をコンソールに出す(読み込み直後に実行)
new PerformanceObserver(function (list) {
var e = list.getEntries().pop();
console.log('LCP:', Math.round(e.startTime), 'ms');
console.log('element:', e.element);
console.log('url:', e.url || '(text)');
if (e.element) e.element.style.outline = '3px solid magenta';
}).observe({ type: 'largest-contentful-paint', buffered: true });
実行すると、LCPになっている要素に枠が付き、それが画像なら url にファイルパスが出ます。ここで (text) と表示されたら、この記事の対象外です。フォントの font-display や先読みを見直してください。
注意点として、LCP要素はビューポート幅で変わります。PCではヒーロー画像、スマホでは見出しテキストがLCPになる、という分岐は普通に起きるので、必ずスマホ幅でも同じ計測をしてから施策を決めます。
WordPressは勝手にlazyを付けている
WordPressは5.5以降、本文とアイキャッチの img に loading="lazy" を自動で付与します。テーマのテンプレートに書いていなくても付くため、「うちはlazyを設定していないから関係ない」は成立しません。実際、テーマ側のアイキャッチ出力に直撃していたケースを何度も見ています。
まずブラウザで実際のHTMLを確認します。開発者ツールのElementsではなく、view-source: で生のHTMLを見るのが確実です(JSで書き換えている場合に区別が付きます)。
先頭の数枚だけ自動付与を止めるには、functions.php(子テーマ)に次を書きます。WordPress 5.9以降で使えるフィルタです。
// 先頭2枚の画像には lazy を付けない(3枚目以降は従来どおり遅延)
add_filter( 'wp_omit_loading_attr_threshold', function () {
return 2;
} );
この「2」はファーストビューに入る画像の枚数に合わせます。ロゴが img で出力されているテーマだと、ロゴが1枚目を消費してアイキャッチが2枚目になるため、1では足りません。実測で、ロゴ+アイキャッチ構成のサイトは2、ロゴがSVGインラインまたはCSS背景のサイトは1が適正でした。ここは推測せず、生HTMLで img の登場順を数えて決めます。
アイキャッチだけを狙って外す
枚数で切ると意図しない画像まで外れる場合は、アイキャッチの出力に絞って属性を上書きします。
// アイキャッチだけ lazy を外し、優先度を上げる
add_filter( 'wp_get_attachment_image_attributes', function ( $attr, $attachment, $size ) {
if ( is_singular() && has_post_thumbnail() && get_post_thumbnail_id() === $attachment->ID ) {
$attr['loading'] = 'eager';
$attr['fetchpriority'] = 'high';
$attr['decoding'] = 'sync';
}
return $attr;
}, 10, 3 );
注意したいのは、この関数はアーカイブページの一覧サムネイルでも走る点です。is_singular() を外すと一覧の全サムネイルが eager になり、今度は一覧ページが重くなります。条件を必ず付けてください。
fetchpriority=”high” は1ページ1枚だけ
fetchpriority="high" は「他より先に取ってこい」という相対的な指示です。複数の画像に付けると優先度が横並びになり、実質的に何も指定していないのと同じになります。効かせたい画像を1枚に絞るのが鉄則です。
<!-- ファーストビューのメイン画像(1ページに1つだけ) -->
<img src="/img/hero.webp" width="1200" height="630" alt="…"
fetchpriority="high" decoding="sync">
<!-- 本文中盤以降の図版 -->
<img src="/img/fig-01.webp" width="800" height="450" alt="…"
loading="lazy" decoding="async">
スライダー(Swiperなど)を置いている場合は、1枚目だけ eager + high、2枚目以降は lazy にします。スライダープラグインの設定で「全スライドを遅延」にしていると1枚目まで遅れるので、プラグイン側の設定も合わせて確認します。
srcset・pictureを使っている場合の先読み
レスポンシブ画像でさらに前倒ししたいときは <head> で先読みします。href だけを書くと srcset と食い違って二重ダウンロードになるため、imagesrcset と imagesizes を必ずセットで書きます。
<link rel="preload" as="image"
href="/img/hero-1200.webp"
imagesrcset="/img/hero-800.webp 800w, /img/hero-1200.webp 1200w"
imagesizes="(max-width: 767px) 100vw, 1200px"
fetchpriority="high">
preloadは「効くが副作用がある」施策です。全ページ共通の <head> に固定パスで書くと、その画像を使っていないページでも無駄にダウンロードします。トップページなど画像が固定されているテンプレートに限定して入れてください。
lazyが効かない・逆効果になるケース
属性を付けたのに挙動が変わらないときは、たいてい次のどれかです。
- CSSの背景画像には効かない。
background-imageはloading属性の対象外です。ヒーローを背景画像で組んでいる場合、遅延も優先度指定もHTMLからは効きません。速度を詰めるならimgかpictureに置き換えるのが先です。 display:noneの中にある画像。ハンバーガーメニューの中身などは、lazyだと開くまで読み込まれず、開いた瞬間に空白が出ます。メニュー内のロゴなど小さく確実に見せたい画像は eager のままにします。- 画像が小さすぎる。数KBのアイコンに lazy を付けても短縮効果はほぼ無く、リクエストの発生タイミングがばらけるだけです。目安として、20KB未満の装飾画像は lazy の対象から外して考えて構いません。
- プラグインの多重付与。高速化系プラグインが独自のlazyload(
data-srcへ退避してJSで差し替える方式)を持っていると、WordPress標準のloading属性と二重にかかり、fetchpriorityを書いてもsrcが空のままなので効きません。この方式のプラグインを使う場合は、プラグイン側の除外設定(先頭N枚を除外/特定クラスを除外)で対応します。両方で制御しようとすると必ず矛盾します。
同時にCLSを潰す:width/heightとaspect-ratio
遅延読み込みを入れると、画像が後から挿し込まれる分だけレイアウトシフト(CLS)が起きやすくなります。読み込み前に高さが確保されていないと、画像が来た瞬間に下のテキストが押し下げられ、読んでいる最中に文章が動きます。表示速度の数値以前に、体感でいちばん嫌われる挙動です。
対策はシンプルで、すべての img に元画像の width と height を書くことです。CSSで width:100% にしていても属性は残します。ブラウザは属性から縦横比を計算して、読み込み前に場所を確保します。
<img src="/img/fig-01.webp" width="800" height="450" alt="…" loading="lazy">
ここで頻発する事故が、CSS側の書き忘れです。
/* NG:属性のheightがそのまま効いて縦が潰れる/伸びる */
img { width: 100%; }
/* OK:比率を保って可変させる */
img {
width: 100%;
height: auto;
}
height:auto が無いと、幅だけ100%に伸びて高さは属性値(例では450px)で固定され、画像が縦に潰れます。「属性を書いたらデザインが崩れた」という相談はほぼこれが原因です。リセットCSSに img{max-width:100%;height:auto;} が入っているかを先に確認してください。
サイズが事前に分からない画像はaspect-ratio
外部APIから引っ張る画像やユーザー投稿など、縦横が事前に確定しない箇所は、枠側で比率を固定します。
.c-thumb {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.c-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
この形なら、画像が来る前から枠の高さが決まるためシフトしません。object-fit:cover は縦横比が違う画像をトリミング表示するので、人物写真では顔が切れることがあります。その場合は object-position: top; を足すと、証明写真的な素材ではほぼ収まります。
なお、幅を固定しないまま画像を並べていて横スクロールが出てしまう場合は、原因が画像とは限りません。特定手順はスマホで横スクロールが出る原因の特定手順にまとめてあります。
作業の順番と公開前チェックリスト
手を付ける順番を間違えると、計測が濁って何が効いたのか分からなくなります。実務では次の順で固定しています。
- ①LCP要素を特定する(PC幅・スマホ幅の両方)。画像でなければ画像施策はやらない。
- ②その1枚だけ lazy を外す。ここで一度計測して差を見る。
- ③fetchpriority=”high” を足す。再計測。効果が出るのはたいていこの2ステップまで。
- ④preloadは最後。テンプレートが固定のページに限る。
- ⑤width/heightとheight:autoを全画像に通す。CLS側の仕上げ。
公開前には次を確認します。
- 生HTML(
view-source:)でfetchpriority="high"が1つだけであること - ファーストビューに映る画像に
loading="lazy"が残っていないこと - 一覧・アーカイブページのサムネイルが eager になっていないこと
- 全
imgにwidthとheightがあり、CSSにheight:autoがあること - スマホ幅で上から下までスクロールし、文章が押し下げられる箇所が無いこと
表示速度は、重い施策を1つ入れるより属性2つを正しい画像に付けるほうが効くことが多い領域です。まずLCP要素の特定から始めてください。サーバー側の応答時間まで含めて詰めたい場合は、Web制作者が選ぶレンタルサーバー比較も併せて確認すると原因の切り分けが速くなります。多言語サイトのように画像とページ数が増える構成では、この差がそのまま離脱率に出ます(多言語サイトの作り方)。
多言語・ベトナム語サイトの制作でお困りではありませんか?
フォント・文字化け・WordPress多言語化・サーバー選定まで、多言語サイト制作の実績を持つ制作会社が一括対応します。お見積り・ご相談は無料です。
多言語サイト制作の詳細を見る →すぐに相談したい方は お問い合わせフォームへ