Ch 06. 이미지 최적화 — next/image
웹 페이지가 느린 이유 중 상당 부분은 이미지 때문입니다. 4000x3000 원본 사진을 그대로 300px 섬네일로 표시하면 엄청난 낭비입니다. 모바일에서도 PC용 고해상도 이미지를 다운로드합니다.
Next.js의 next/image는 이 문제를 자동으로 해결합니다.
next/image의 장점
<img> 태그 대신 <Image> 컴포넌트를 사용하면 자동으로 얻는 것들이 있습니다.
- 자동 크기 최적화: 표시 크기에 맞게 이미지를 리사이즈합니다.
- 포맷 변환: 브라우저가 지원하면 WebP, AVIF 등 현대 포맷으로 변환합니다.
- 지연 로딩(Lazy Loading): 뷰포트 밖의 이미지는 스크롤해서 가까워질 때 로드합니다.
- CLS 방지: 이미지 크기를 미리 지정해 레이아웃 이동을 막습니다.
기본 사용법
// 파일: components/PostCard.tsximport Image from "next/image";type Post = { title: string; thumbnail: string; author: string; date: string;};export function PostCard({ post }: { post: Post }) { return ( <article className="rounded-lg overflow-hidden border"> {/* width와 height는 필수입니다 */} <Image src={post.thumbnail} alt={`${post.title} 썸네일`} width={800} height={450} className="w-full object-cover" /> <div className="p-4"> <h3 className="font-semibold text-lg">{post.title}</h3> <p className="text-sm text-gray-500 mt-1"> {post.author} · {post.date} </p> </div> </article> );}```text`width`와 `height`는 픽셀 단위의 원본 크기가 아니라, 렌더링 시 사용할 비율의 기준입니다. 실제 표시 크기는 CSS로 제어합니다.### fill 속성 — 컨테이너에 맞게부모 요소의 크기를 미리 알 수 없을 때는 `fill` 속성을 사용합니다. 부모 요소가 `position: relative`여야 합니다.```typescript// 파일: components/HeroBanner.tsximport Image from "next/image";export function HeroBanner({ imageUrl }: { imageUrl: string }) { return ( <div className="relative w-full h-64 md:h-96"> <Image src={imageUrl} alt="히어로 배너" fill className="object-cover" priority // 중요한 이미지는 즉시 로드 /> </div> );}```text`priority`를 추가하면 지연 로딩을 비활성화하고 즉시 로드합니다. 화면 상단에 바로 보이는 중요한 이미지(LCP 대상)에 사용합니다.### 외부 이미지 도메인 허용보안상의 이유로, 외부 URL의 이미지는 `next.config.ts`에서 허용 도메인을 명시해야 합니다.```typescript// 파일: next.config.tsimport type { NextConfig } from "next";const nextConfig: NextConfig = { images: { remotePatterns: [ { protocol: "https", hostname: "images.unsplash.com", pathname: "/**", }, { protocol: "https", hostname: "avatars.githubusercontent.com", pathname: "/**", }, { // 와일드카드로 모든 서브도메인 허용 protocol: "https", hostname: "**.example.com", }, ], },};export default nextConfig;```text허용하지 않은 도메인의 이미지를 사용하면 빌드는 성공하지만 런타임에 오류가 발생합니다. `hostname` 패턴을 정확히 설정해야 합니다.### placeholder="blur"로 로딩 UX 개선이미지가 로드되기 전 빈 공간 대신 흐릿한 미리보기를 보여줍니다.로컬 파일은 자동으로 blur 이미지를 생성합니다.```typescript// 파일: components/LocalImageExample.tsximport Image from "next/image";import profilePic from "@/public/profile.jpg"; // 로컬 파일 importexport function LocalImageExample() { return ( <Image src={profilePic} alt="프로필 사진" placeholder="blur" // 로컬 파일은 blurDataURL 자동 생성 className="rounded-full" /> );}```text외부 이미지는 `blurDataURL`을 직접 제공해야 합니다. 보통 매우 작은 Base64 인코딩 이미지를 사용합니다.```typescript// 파일: components/ExternalImageExample.tsximport Image from "next/image";// 10x10 픽셀 회색 placeholder (Base64)const BLUR_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mNkYPhfz0AEYBxVSF+FABJaAyhbSUEDAAAAAElFTkSuQmCC";export function ExternalImageExample({ imageUrl }: { imageUrl: string }) { return ( <Image src={imageUrl} alt="외부 이미지" width={800} height={450} placeholder="blur" blurDataURL={BLUR_DATA_URL} className="w-full rounded-lg" /> );}```text### sizes 속성으로 반응형 최적화`sizes` 속성을 사용하면 브라우저가 뷰포트 크기에 따라 적절한 이미지를 선택합니다.```typescript// 파일: components/ResponsiveImage.tsximport Image from "next/image";export function ResponsiveImage({ src, alt }: { src: string; alt: string }) { return ( <Image src={src} alt={alt} width={1200} height={630} sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" className="w-full" /> );}
- 768px 이하: 뷰포트 너비 100%
- 768~1200px: 뷰포트 너비 50%
- 1200px 초과: 뷰포트 너비 33%
이 정보를 기반으로 Next.js가 각 상황에 맞는 최적의 크기 이미지를 제공합니다.
다음 챕터에서는 SEO 최적화를 위한 metadata API를 살펴봅니다.