1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

ポートフォリオサイトにフォトギャラリーを実装する際、画像の読み込みパフォーマンスは重要な課題です。本記事では、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枚の高解像度画像を含むギャラリーでも、ページの初期ロード時間に影響を与えることなく、スムーズなユーザー体験を提供できました。

参考リンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?