jngmnj's blog

jngmnj's blog Logo

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 컴포넌트는 웹 성능 최적화의 핵심 도구입니다.

기억해야 할 핵심 포인트

  1. 항상 width/height 지정 - 레이아웃 시프트 방지
  2. priority 선택적 사용 - 중요한 이미지만 우선 로딩
  3. 적절한 sizes 설정 - 반응형 이미지 최적화
  4. placeholder 활용 - 사용자 경험 향상
  5. 외부 이미지 설정 - remotePatterns 올바른 구성

이러한 최적화를 통해 더 빠르고 사용자 친화적인 웹사이트를 만들 수 있습니다. 실제 프로젝트에서는 각 이미지의 역할과 중요도를 고려하여 적절한 옵션을 선택하는 것이 중요합니다.