이미지 최적화
규칙
- 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 타입 확인