📝

Next.js 15でのフォトギャラリー実装とパフォーマンス最適化

に公開

はじめに

ポートフォリオサイトにフォトギャラリーを実装する際、画像の読み込みパフォーマンスは重要な課題です。本記事では、Next.js 15を使用したフォトギャラリーの実装と、そのパフォーマンス最適化について解説します。

実装したフォトギャラリーの概要

今回実装したフォトギャラリーには以下の機能があります:

  • カテゴリー別フィルタリング(研究、ビジネス、日常、アウトドア)
  • モーダル表示での拡大表示
  • キーボード・マウスでのナビゲーション
  • レスポンシブデザイン

Next.js Imageコンポーネントによる最適化

基本的な実装

<Image
  src={image.src}
  alt={image.alt}
  fill
  className="object-cover transition-transform duration-300 group-hover:scale-110"
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 25vw"
  loading="lazy"
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
/>

最適化のポイント

1. sizes属性の適切な設定

sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 25vw"

この設定により:

  • モバイル(768px以下): 画面幅の100%
  • タブレット(1200px以下): 画面幅の50%
  • デスクトップ: 画面幅の25%

Next.jsが自動的に最適なサイズの画像を生成・配信します。

2. 遅延読み込み(Lazy Loading)

loading="lazy"

デフォルトでも遅延読み込みは有効ですが、明示的に指定することで意図を明確にしています。

3. プレースホルダーの実装

placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."

超軽量(約1KB)のBase64エンコード画像を使用し、画像読み込み中のユーザー体験を向上させます。

パフォーマンス測定結果

初期ロードへの影響

ページ位置による影響:
- ギャラリーセクション: ページ下部に配置
- 初期ビューポート: ギャラリーは含まれない
- 結果: 初期ロード時間への影響なし

画像読み込みのタイミング

// Intersection Observerによる監視イメージ
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // 画像がビューポートに入った時点で読み込み開始
      loadImage(entry.target);
    }
  });
}, {
  rootMargin: '50px' // 50px手前から読み込み開始
});

実装コードの詳細

ギャラリーコンポーネントの構造

const GallerySection = () => {
  const [selectedImage, setSelectedImage] = useState<number | null>(null);
  const [activeCategory, setActiveCategory] = useState("all");

  const images = [
    {
      src: "/lab.jpeg",
      alt: "Research Activity",
      category: "research",
      titleKey: "research",
      description: "京都大学での研究活動の様子",
    },
    // ... 他の画像
  ];

  const filteredImages = activeCategory === "all"
    ? images
    : images.filter((img) => img.category === activeCategory);

  return (
    <section>
      {/* カテゴリーフィルター */}
      {/* 画像グリッド */}
      {/* モーダル */}
    </section>
  );
};

モーダル表示の最適化

モーダルで表示する画像は、より大きなサイズが必要です:

<Image
  src={filteredImages[selectedImage].src}
  alt={filteredImages[selectedImage].alt}
  width={1000}
  height={700}
  className="w-auto h-auto max-h-[70vh] object-contain"
/>

さらなる最適化のアイデア

1. 動的インポートによるコード分割

const GallerySection = dynamic(
  () => import("@/components/GallerySection"),
  {
    loading: () => <div>Loading gallery...</div>,
    ssr: false
  }
);

2. 画像の事前最適化

# Sharp CLIを使用した例
npx sharp-cli resize 1200 1200 --fit inside --withoutEnlargement \
  --quality 85 --format webp input.jpg output.webp

3. リソースヒントの活用

// 次の画像を事前に接続
<link rel="preconnect" href="/_next/image" />
<link rel="dns-prefetch" href="/_next/image" />

4. Progressive Enhancementアプローチ

// 低解像度画像を最初に表示
const [imageQuality, setImageQuality] = useState<"low" | "high">("low");

useEffect(() => {
  // ネットワーク状況を確認
  if (navigator.connection?.effectiveType === '4g') {
    setImageQuality("high");
  }
}, []);

ベストプラクティス

1. 画像フォーマットの選択

// Next.jsは自動的に最適なフォーマットを選択
// 優先順位: AVIF > WebP > オリジナル形式

2. アスペクト比の維持

<div className="relative aspect-square">
  <Image
    fill
    className="object-cover"
    // アスペクト比を維持しながらコンテナにフィット
  />
</div>

3. エラーハンドリング

const [imageError, setImageError] = useState<Record<number, boolean>>({});

<Image
  onError={() => {
    setImageError(prev => ({ ...prev, [index]: true }));
  }}
  src={imageError[index] ? "/placeholder.jpg" : image.src}
/>

パフォーマンス測定ツール

Lighthouse スコア

Performance: 95+
- First Contentful Paint: 0.8s
- Largest Contentful Paint: 1.2s
- Total Blocking Time: 30ms
- Cumulative Layout Shift: 0.001

実際の測定コード

// パフォーマンス測定用のカスタムフック
const useImageLoadTime = () => {
  const [loadTimes, setLoadTimes] = useState<Record<string, number>>({});

  const measureLoad = (src: string) => {
    const startTime = performance.now();
    
    return () => {
      const loadTime = performance.now() - startTime;
      setLoadTimes(prev => ({ ...prev, [src]: loadTime }));
    };
  };

  return { loadTimes, measureLoad };
};

まとめ

Next.js 15のImageコンポーネントを適切に使用することで、フォトギャラリーのパフォーマンスを大幅に改善できます。重要なポイントは:

  1. 適切なsizes属性の設定 - レスポンシブ画像の最適化
  2. 遅延読み込みの活用 - 初期ロードへの影響を最小化
  3. プレースホルダーの実装 - ユーザー体験の向上
  4. 画像フォーマットの自動選択 - Next.jsの最適化機能を活用

これらの最適化により、4枚の高解像度画像を含むギャラリーでも、ページの初期ロード時間に影響を与えることなく、スムーズなユーザー体験を提供できました。

参考リンク

GitHubで編集を提案

Discussion