画像最適化
ルール
- Next.js ではすべての画像に
next/image を使用 — 自動最適化、遅延読み込み、レスポンシブ srcset を実現
- 常に
width と height を設定するか、fill をサイズ指定コンテナと併用 — レイアウトシフト (CLS) を防止
- ぼかしプレースホルダー: ビルド時またはアップロード時に
plaiceholder で blurDataURL を生成、静的インポートでは placeholder="blur" を使用
- リモート画像:
next.config.js で remotePatterns を設定 — CDN ドメインをホワイトリストに追加
- アップロードフロー: クライアント → 署名付き URL (S3/R2/Supabase Storage) → 直接アップロード → URL をデータベースに保存
- アップロード時にリサイズ:
sharp をサーバーサイドで使用して複数のサイズ(サムネイル、中、大)を生成 — 元画像は配信しない
- フォーマット:
next/image の自動フォーマットで WebP/AVIF を提供 — 互換性のために JPEG にフォールバック
- CDN 配信: 元画像をオブジェクトストレージ (S3、R2) に保存し、CDN (CloudFront、Cloudflare) を通じてキャッシュヘッダー付きで配信
- 最大ファイルサイズバリデーション: クライアント側(アップロード前)とサーバー側(処理前)で確認 — 通常 5〜10MB が上限
パターン
import Image from "next/image";
<Image
src="/hero.jpg"
alt="ヒーロー画像"
width={1200}
height={630}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..."
priority // ファーストビュー
/>
<div className="relative aspect-video">
<Image src={url} alt={alt} fill className="object-cover" sizes="(max-width: 768px) 100vw, 50vw" />
</div>
import sharp from "sharp";
async function processUpload(buffer: Buffer) {
const thumbnail = await sharp(buffer).resize(200, 200, { fit: "cover" }).webp({ quality: 80 }).toBuffer();
const medium = await sharp(buffer).resize(800).webp({ quality: 85 }).toBuffer();
const full = await sharp(buffer).resize(1920).webp({ quality: 90 }).toBuffer();
return { thumbnail, medium, full };
}
避けるべきこと
- Next.js で
<img> タグを使用 — 自動最適化、遅延読み込み、フォーマット変換が失われる
- レスポンシブ画像で
sizes プロパティを省略 — ブラウザが適切な srcset サイズを選択できない
- リサイズしていない元画像を配信 — 400px で表示するのに 5MB の写真は無駄が多い
- ファイルタイプバリデーションをスキップ — ファイル拡張子だけでなくサーバーサイドで MIME タイプを確認