はじめに
ポートフォリオサイトにフォトギャラリーを実装する際、画像の読み込みパフォーマンスは重要な課題です。本記事では、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コンポーネントを適切に使用することで、フォトギャラリーのパフォーマンスを大幅に改善できます。重要なポイントは:
-
適切な
sizes
属性の設定 - レスポンシブ画像の最適化 - 遅延読み込みの活用 - 初期ロードへの影響を最小化
- プレースホルダーの実装 - ユーザー体験の向上
- 画像フォーマットの自動選択 - Next.jsの最適化機能を活用
これらの最適化により、4枚の高解像度画像を含むギャラリーでも、ページの初期ロード時間に影響を与えることなく、スムーズなユーザー体験を提供できました。