<CodeLearn/>
パフォーマンス レッスン3

画像・アセット最適化

WebP/AVIF、レスポンシブ画像、lazy loading、フォント最適化を学ぼう

モダンな画像フォーマット

画像はWebページの転送量の大部分を占めます。WebPAVIFなどの モダンフォーマットを使うことで、画質を維持しつつファイルサイズを大幅に削減できます。

■ フォーマット比較(同じ画質での圧縮率目安)

  フォーマット    サイズ    ブラウザ対応    用途
  ─────────────────────────────────────────────
  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を活用し、バンドルサイズを最小限に保つ