Next.js 이미지 최적화 완벽 가이드

지정민
웹 페이지에서 이미지는 종종 가장 큰 용량을 차지하는 리소스입니다. Next.js의 Image 컴포넌트는 이런 문제를 해결하기 위해 다양한 최적화 기능을 제공합니다. 실제 프로젝트에서 바로 활용할 수 있는 이미지 최적화 방법들을 살펴보겠습니다.
왜 Next.js Image를 사용해야 할까?
일반적인 <img> 태그 대신 Next.js Image 컴포넌트를 사용하면 다음과 같은 이점을 얻을 수 있습니다.
- 자동 포맷 최적화: WebP, AVIF 같은 최신 포맷으로 자동 변환
- 지연 로딩: 뷰포트에 들어올 때만 이미지 로드
- 레이아웃 시프트 방지: CLS(Cumulative Layout Shift) 개선
- 반응형 이미지: 디바이스에 맞는 크기로 자동 조절
기본 사용법
1. 정적 이미지 (Static Images)
빌드타임에 결정됩니다.
import Image from 'next/image';
import profilePic from '/public/profile.jpg';
export default function Profile() {
return (
<Image src={profilePic} alt="Profile Picture" width={500} height={500} />
);
}2. 동적 이미지 (Dynamic Images)
동적 이미지는 런타임에 결정되는 이미지를 말합니다. 주로 API, 데이터베이스, 사용자 입력에서 오는 이미지들입니다.
import Image from 'next/image';
export default function Gallery({ images }) {
return (
<div>
{images.map((image) => (
<Image
key={image.id}
src={image.src} // 동적으로 결정되는 URL
alt={image.alt}
width={300}
height={200}
/>
))}
</div>
);
}핵심 최적화 옵션들
priority - 우선 로딩
페이지에서 가장 중요한 이미지(예: 히어로 이미지)에 사용합니다.
<Image
src="/hero-image.jpg"
alt="Hero Image"
width={1920}
height={1080}
priority // LCP(Largest Contentful Paint) 개선
/>placeholder - 로딩 플레이스홀더
이미지 로딩 중 표시할 플레이스홀더를 설정합니다.
// Blur 플레이스홀더
<Image
src="/profile.jpg"
alt="Profile"
width={400}
height={400}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/..."
/>
// 빈 플레이스홀더
<Image
src="/profile.jpg"
alt="Profile"
width={400}
height={400}
placeholder="empty"
/>sizes - 반응형 이미지
다양한 뷰포트에서 이미지가 차지할 크기를 명시합니다.
브라우저는 sizes 정보를 바탕으로 어떤 크기의 이미지를 다운로드할지 결정합니다.
sizes가 없으면 브라우저가 추측합니다.
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
<Image
src="/post-thumbnail.jpg"
alt="Post Thumbnail"
width={400}
height={300}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
// 모바일: 1열(100%), 태블릿: 2열(50%), 데스크톱: 3열(33%)
/>
</div>fill - 부모 컨테이너에 맞추기
이미지를 부모 컨테이너 크기에 맞춰 조절합니다.
<div className="relative h-64 w-full">
<Image
src="/cover-image.jpg"
alt="Cover Image"
fill
className="rounded-lg object-cover"
/>
</div>실전 활용 예시
1. 프로필 이미지 최적화
// 프로필 이미지 컴포넌트
function ProfileImage({ src, alt, size = 'md' }) {
const sizeMap = {
sm: { width: 40, height: 40, class: 'w-10 h-10' },
md: { width: 64, height: 64, class: 'w-16 h-16' },
lg: { width: 128, height: 128, class: 'w-32 h-32' },
};
const { width, height, class: className } = sizeMap[size];
return (
<div className={`relative ${className} overflow-hidden rounded-full`}>
<Image
src={src}
alt={alt}
fill
className="object-cover"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
sizes={`${width}px`}
/>
</div>
);
}2. 반응형 갤러리
function ResponsiveGallery({ images }) {
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{images.map((image, index) => (
<div key={image.id} className="relative aspect-square">
<Image
src={image.src}
alt={image.alt}
fill
className="rounded-lg object-cover"
placeholder="blur"
blurDataURL={image.blurDataURL}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority={index < 3} // 처음 3개 이미지만 우선 로딩
/>
</div>
))}
</div>
);
}3. 히어로 섹션
function HeroSection() {
return (
<div className="relative h-screen">
<Image
src="/hero-background.jpg"
alt="Hero Background"
fill
className="object-cover"
priority
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
quality={90} // 히어로 이미지는 높은 품질 사용
/>
<div className="relative z-10 flex h-full items-center justify-center">
<h1 className="text-4xl font-bold text-white">Welcome</h1>
</div>
</div>
);
}외부 이미지 처리
외부 도메인의 이미지를 사용할 때는 next.config.js에 설정이 필요합니다.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '*.amazonaws.com',
},
],
},
};
module.exports = nextConfig;성능 최적화 팁
1. 적절한 이미지 크기 설정
// ❌ 너무 큰 이미지
<Image src="/image.jpg" width={2000} height={2000} />
// ✅ 실제 표시 크기에 맞춤
<Image src="/image.jpg" width={400} height={400} />2. 올바른 sizes 속성 사용
// ❌ sizes 속성 없음 - 불필요한 큰 이미지 로드 가능
<Image src="/image.jpg" width={800} height={600} />
// ✅ 반응형 sizes 설정
<Image
src="/image.jpg"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
/>3. 우선순위 설정
function ProductPage({ product }) {
return (
<div>
{/* 메인 제품 이미지 - 우선 로딩 */}
<Image
src={product.mainImage}
alt={product.name}
width={600}
height={400}
priority
/>
{/* 추가 이미지들 - 지연 로딩 */}
{product.additionalImages.map((img) => (
<Image
key={img.id}
src={img.src}
alt={img.alt}
width={200}
height={150}
// priority 속성 없음 - 지연 로딩
/>
))}
</div>
);
}일반적인 실수와 해결법
1. Layout Shift 문제
// ❌ width, height 없음 - 레이아웃 시프트 발생
<Image src="/image.jpg" alt="Image" />
// ✅ 명시적 크기 설정
<Image src="/image.jpg" alt="Image" width={400} height={300} />
// ✅ 또는 fill 사용 (부모 컨테이너가 크기를 가져야 함)
<div className="relative w-96 h-64">
<Image src="/image.jpg" alt="Image" fill className="object-cover" />
</div>2. 외부 이미지 설정 누락
// ❌ next.config.js 설정 없이 외부 이미지 사용
<Image src="https://example.com/image.jpg" width={400} height={300} />
// ✅ next.config.js에 remotePatterns 설정 후 사용
<Image src="https://example.com/image.jpg" width={400} height={300} />정리
Next.js Image 컴포넌트는 웹 성능 최적화의 핵심 도구입니다.
기억해야 할 핵심 포인트
- 항상 width/height 지정 - 레이아웃 시프트 방지
- priority 선택적 사용 - 중요한 이미지만 우선 로딩
- 적절한 sizes 설정 - 반응형 이미지 최적화
- placeholder 활용 - 사용자 경험 향상
- 외부 이미지 설정 - remotePatterns 올바른 구성
이러한 최적화를 통해 더 빠르고 사용자 친화적인 웹사이트를 만들 수 있습니다. 실제 프로젝트에서는 각 이미지의 역할과 중요도를 고려하여 적절한 옵션을 선택하는 것이 중요합니다.