パフォーマンス レッスン3
画像・アセット最適化
WebP/AVIF、レスポンシブ画像、lazy loading、フォント最適化を学ぼう
モダンな画像フォーマット
画像はWebページの転送量の大部分を占めます。WebPやAVIFなどの モダンフォーマットを使うことで、画質を維持しつつファイルサイズを大幅に削減できます。
■ フォーマット比較(同じ画質での圧縮率目安)
フォーマット サイズ ブラウザ対応 用途
─────────────────────────────────────────────
JPEG 100% 全ブラウザ 写真
PNG 120% 全ブラウザ 透過画像、スクリーンショット
WebP 70% 95%以上 写真・イラスト全般
AVIF 50% 85%以上 写真(最高圧縮率)
SVG - 全ブラウザ アイコン、ロゴ(ベクター)
■ <picture> 要素でフォールバック対応
<picture>
<!-- AVIF対応ブラウザ向け -->
<source srcset="/hero.avif" type="image/avif" />
<!-- WebP対応ブラウザ向け -->
<source srcset="/hero.webp" type="image/webp" />
<!-- フォールバック -->
<img src="/hero.jpg" alt="ヒーロー画像" />
</picture>レスポンシブ画像(srcset)
srcsetを使うと、デバイスの画面サイズやピクセル密度に応じて 適切なサイズの画像を配信できます。モバイルに巨大な画像を送るのを防ぎます。
<!-- 画面幅に応じた画像切り替え -->
<img
srcset="
/photo-400w.webp 400w,
/photo-800w.webp 800w,
/photo-1200w.webp 1200w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
src="/photo-800w.webp"
alt="写真"
width="800"
height="600"
/>
<!-- ピクセル密度に応じた切り替え(Retinaディスプレイ対応)-->
<img
srcset="
/logo.png 1x,
/logo@2x.png 2x,
/logo@3x.png 3x
"
src="/logo.png"
alt="ロゴ"
width="200"
height="50"
/>
<!-- 重要: width と height を必ず指定する -->
<!-- → ブラウザが事前にスペースを確保し、CLS を防げる -->Lazy Loading
Lazy Loadingは、ビューポートに近づいたときに初めて 画像を読み込む手法です。初期読み込みのデータ量を大幅に削減できます。
<!-- ネイティブ Lazy Loading(推奨) -->
<img
src="/photo.webp"
alt="写真"
loading="lazy"
width="800"
height="600"
/>
<!-- 注意: ファーストビューの画像には loading="lazy" を付けない -->
<!-- LCP 要素が遅延読み込みされるとスコアが悪化する -->
<img
src="/hero.webp"
alt="ヒーロー画像"
loading="eager"
fetchpriority="high"
width="1200"
height="600"
/>
<!-- iframe の Lazy Loading -->
<iframe
src="https://www.youtube.com/embed/..."
loading="lazy"
title="動画"
></iframe>
<!-- Intersection Observer による手動実装 -->
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, { rootMargin: '200px' }); // 200px手前で読み込み開始
document.querySelectorAll('img[data-src]').forEach((img) => {
observer.observe(img);
});
</script>Next.js の Image コンポーネント
Next.jsの<Image>コンポーネントは、 画像最適化のベストプラクティスを自動で適用してくれます。
import Image from 'next/image';
// 基本的な使い方
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="ヒーロー画像"
width={1200}
height={600}
priority // LCP要素にはpriorityを指定
placeholder="blur" // ぼかしプレースホルダー
blurDataURL="data:image/jpeg;base64,..."
/>
);
}
// fill モード(親要素いっぱいに広がる)
export function BackgroundImage() {
return (
<div style={{ position: 'relative', width: '100%', height: '400px' }}>
<Image
src="/bg.jpg"
alt="背景"
fill
style={{ objectFit: 'cover' }}
sizes="100vw"
/>
</div>
);
}
// next/image が自動で行うこと:
// ✅ WebP/AVIF への自動変換
// ✅ リクエストに応じたリサイズ
// ✅ lazy loading(デフォルト有効)
// ✅ CLS 防止(width/height の必須指定)
// ✅ srcset の自動生成フォント最適化とTree Shaking
Webフォントとバンドルサイズの最適化も重要なパフォーマンス改善ポイントです。
■ フォント最適化
// Next.js の next/font(推奨)
import { Inter, Noto_Sans_JP } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // FOIT を防ぐ
preload: true,
});
const notoSansJP = Noto_Sans_JP({
subsets: ['latin'],
weight: ['400', '700'], // 必要なウェイトのみ
display: 'swap',
});
// CSS での font-display 設定
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap; /* テキストを先に表示 */
unicode-range: U+0000-00FF; /* 必要な文字だけ読み込み */
}
■ Tree Shaking(未使用コードの除去)
// ❌ ライブラリ全体をインポート(バンドルサイズ大)
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ 必要な関数だけインポート(Tree Shaking 対応)
import debounce from 'lodash/debounce';
debounce(fn, 300);
// ❌ アイコンライブラリ全体(数百KB)
import { FaHome } from 'react-icons/fa';
// ✅ 個別インポート
import { FaHome } from 'react-icons/fa';
// → バンドラーが未使用アイコンを除去
// バンドル分析ツール
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
// 実行: ANALYZE=true npm run buildまとめ
- WebP/AVIFを使い、pictureタグでフォールバックを提供する
- srcset と sizes で画面サイズに合った画像を配信する
- ファーストビュー外の画像には loading="lazy" を適用する
- Next.jsのImageコンポーネントが多くの最適化を自動で行う
- font-display: swap でフォント読み込み中もテキストを表示する
- Tree Shakingを活用し、バンドルサイズを最小限に保つ