안동민 개발노트

안동민 개발노트

이미지 최적화폰트 최적화코드 분할 및 지연 로딩메모이제이션 및 리렌더링 최적화
본문 시작
  1. 홈
  2. 문서
  3. Next.js
  4. 12장 : 성능 최적화
  5. 이미지 최적화
  1. Next.js
  2. 이미지 최적화

이미지 최적화

next/image의 크기·우선순위·반응형 속성과 외부 호스트 설정으로 레이아웃 이동과 이미지 전송량을 줄입니다.

웹 애플리케이션의 성능 최적화는 사용자 경험과 SEO에 모두 영향을 주는 중요한 작업입니다.

특히 최적화하지 않은 큰 이미지는 로딩 속도를 늦추므로, 표시 크기와 전송량을 함께 살펴야 합니다.

Next.js는 내장 컴포넌트 next/image를 통해 고성능 이미지 처리를 쉽게 구현할 수 있게 해줍니다.

이 절에서는 이미지 최적화의 중요성부터 next/image 컴포넌트의 사용법, 그리고 이미지 최적화를 위한 다양한 기법들을 상세히 알아보겠습니다.


왜 이미지를 최적화해야 하나요?

이미지는 웹 페이지에서 시각적인 매력을 더하고 정보를 전달하는 데 필수적이지만, 동시에 페이지 로딩 속도를 늦추는 주범이 되기도 합니다.

  • 느린 로딩 속도: 용량이 큰 이미지는 네트워크를 통해 전송되는 데 시간이 오래 걸려 페이지 로딩을 지연시킵니다. 이는 사용자 이탈률 증가로 이어질 수 있습니다.
  • 데이터 사용량 증가: 모바일 환경에서는 사용자의 데이터 요금 부담을 가중시킬 수 있습니다.
  • Core Web Vitals 저하: 이미지 지연과 공간 이동은 LCP·CLS 같은 Core Web Vitals 및 페이지 경험에 영향을 줄 수 있습니다. 이 지표만으로 검색 순위가 정해지는 것은 아닙니다.
  • 불필요한 리소스 낭비: 사용자 기기의 성능에 맞지 않는 고해상도 이미지를 전송하는 것은 불필요한 리소스 낭비입니다.

이미지 최적화는 이러한 문제들을 해결하여 웹 성능을 크게 개선하는 데 기여합니다.


next/image 컴포넌트 사용하기

Next.js는 이미지 최적화를 위해 특별히 설계된 <Image> 컴포넌트를 next/image에서 제공합니다.

기본 이미지 로더는 요청에 맞춰 크기·품질·형식을 변환합니다. 정적 import의 크기 정보와 blur 데이터 준비는 이 요청 시점의 변환과 구분합니다.

주요 최적화 기능
  • 반응형 이미지: 기기 크기에 따라 최적화된 크기의 이미지를 자동으로 제공합니다.
  • 지연 로딩(Lazy Loading): 브라우저가 정한 뷰포트 주변 거리까지 이미지 로딩을 미룹니다.
  • 이미지 형식 최적화: 기본 설정은 브라우저가 지원하면 WebP로 변환합니다. AVIF까지 제공하려면 images.formats에 image/avif를 추가합니다.
  • 이미지 품질 최적화: 지정된 품질 설정에 따라 이미지 압축을 수행합니다.
  • 공간 예약: 고유 크기나 부모 영역을 지정해 이미지가 도착할 자리를 확보합니다. 주변 CSS에 의한 이동까지 모두 제거하는 것은 아닙니다.
  • 이미지 호스팅 최적화: Next.js 서버 또는 CDN을 통해 이미지를 효율적으로 제공합니다.

next/image 설치

Next.js 13 이상을 사용하는 App Router 프로젝트라면 별도의 설치 없이 바로 사용할 수 있습니다.

기본 사용법

<Image> 컴포넌트에는 src와 의미에 맞는 alt가 필요합니다.

public 문자열 경로나 원격 URL을 사용하면 width와 height로 비율을 알려야 하지만, 정적 import가 크기를 제공하거나 fill로 부모 영역을 채우는 경우에는 두 속성을 생략합니다.

public/logo.png를 준비하고, 코드 끝 주석의 CSS는 별도 src/app/page.module.css 파일에 저장합니다. 원격 이미지의 크기 속성은 사용하는 실제 이미지 비율에 맞춥니다.

src/app/page.tsx 또는 다른 컴포넌트 파일
import Image from 'next/image';
import styles from './page.module.css'; // CSS 모듈 사용 예시

export default function HomePage() {
  return (
    <div className={styles.container}>
      <h1>Next.js 이미지 최적화</h1>

      {/* 로컬 이미지 사용 */}
      <div className={styles.imageWrapper}>
        <h2>로컬 이미지</h2>
        <Image
          src="/logo.png"
          alt="Next.js 로고"
          width={500}
          height={300}
          placeholder="blur"
          blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
          style={{ maxWidth: '100%', height: 'auto' }}
        />
        <p>이 이미지는 Next.js 로고입니다.</p>
      </div>

      {/* 외부 이미지 사용 */}
      <div className={styles.imageWrapper}>
        <h2>외부 이미지</h2>
        <Image
          src="https://images.unsplash.com/photo-1617424699564-90f7a77e9b40?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
          alt="아름다운 자연 풍경"
          width={800}
          height={600}
          preload
          style={{ maxWidth: '100%', height: 'auto' }}
        />
        <p>이 이미지는 Unsplash에서 가져온 풍경 이미지입니다.</p>
      </div>

      {/* 레이아웃 채우기 (fill) 예시 */}
      <div className={styles.fillImageWrapper}>
        <h2>레이아웃 채우기 (fill)</h2>
        <div style={{ position: 'relative', width: '100%', height: '300px' }}>
          <Image
            src="https://images.unsplash.com/photo-1610214644596-f94d3f572a8c?q=80&w=2835&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
            alt="도시 야경"
            fill
            sizes="(max-width: 900px) 100vw, 900px"
            style={{ objectFit: 'cover' }}
          />
        </div>
        <p>이 이미지는 부모 요소의 공간을 채웁니다.</p>
      </div>
    </div>
  );
}

// src/app/page.module.css
/*
.container {
  padding: 2rem;
  max-width: 900px;
  margin: 0 auto;
  font-family: Arial, sans-serif;
}

.imageWrapper {
  margin-bottom: 3rem;
  border: 1px solid #ddd;
  padding: 1rem;
  text-align: center;
  background-color: #f9f9f9;
}

.imageWrapper h2 {
  margin-top: 0;
  color: #333;
}

.imageWrapper p {
  font-size: 0.9em;
  color: #666;
  margin-top: 1rem;
}

.fillImageWrapper {
  margin-bottom: 3rem;
  border: 1px solid #007bff;
  padding: 1rem;
  text-align: center;
  background-color: #eaf6ff;
}

.fillImageWrapper h2 {
  margin-top: 0;
  color: #007bff;
}

.fillImageWrapper p {
  font-size: 0.9em;
  color: #666;
  margin-top: 1rem;
}
*/

실습의 fill 이미지는 한 열 전체를 채웁니다. sizes는 컨테이너의 최대 폭 900px을 기준으로 둔 보수적인 상한이며, padding·테두리를 뺀 실제 표시 폭보다 클 수 있습니다. 배포 화면의 폭에 맞춰 더 정확히 좁힐 수 있습니다.

둘째 이미지의 preload는 사용법을 보여주는 설정입니다. 이 이미지가 실제 LCP 요소인지는 화면 크기와 콘텐츠로 확인해야 합니다.

<Image> 컴포넌트의 주요 속성

  • src: 이미지 파일의 경로 (로컬 또는 외부 URL).
  • alt: 이미지에 대한 대체 텍스트. 접근성 및 SEO에 필수적입니다.
  • width, height: 이미지의 고유한 너비와 높이(픽셀). 정적 임포트나 fill을 사용하지 않는 경우 필수이며, 브라우저가 비율을 미리 확보해 CLS를 막도록 돕습니다.
  • preload: 실제 LCP 후보가 명확할 때 <Image preload />로 이미지 발견 시점을 앞당깁니다. Next.js 16에서는 기존 priority 대신 이 속성을 사용합니다. 일반적인 상단 이미지는 loading="eager"나 fetchPriority="high"가 더 적합할 수 있습니다.
  • fill: (불리언) 부모 요소의 크기에 맞춰 이미지를 채웁니다. 이 경우 width와 height 속성은 필요 없으며, 부모 요소에 position: relative 등의 스타일이 적용되어야 합니다.
  • sizes: fill을 사용하거나 CSS로 반응형 크기를 정할 때 실제 표시 폭을 브라우저에 알려 적절한 srcset 후보를 선택하게 합니다. 이 값은 CSS의 표시 폭 자체를 바꾸지 않습니다.
  • quality: 이미지 압축 품질을 1에서 100 사이의 숫자로 지정합니다. Next.js 16은 next.config의 images.qualities 허용 목록을 적용하며 기본 허용값은 75입니다. 다른 값을 사용하려면 허용 목록에도 추가합니다.
  • placeholder: 이미지가 로딩되는 동안 표시될 자리표시자.
    • blur: 작은 blurDataURL을 사용하여 흐릿한 이미지 효과를 보여줍니다. 지원 형식의 정적 import는 blur 데이터를 자동으로 제공하지만, 이 효과는 placeholder="blur"로 선택합니다.
    • empty: 별도의 placeholder를 표시하지 않습니다.
  • blurDataURL: placeholder="blur"와 함께 사용할 작은 Base64 인코딩 이미지 데이터 URL. import hero from "./hero.jpg"처럼 지원 형식의 이미지를 정적으로 import하면 Next.js가 자동 생성합니다. src="/logo.png" 같은 public 문자열 경로나 외부 이미지는 직접 제공해야 합니다.
  • loading: lazy (기본값, 지연 로딩) 또는 eager (즉시 로딩)를 지정합니다. preload와 동시에 지정하지 않습니다.
  • unoptimized: (불리언) 크기·품질·형식 변환 없이 src의 원본을 제공합니다. 레이아웃이나 다른 이미지 속성까지 없애는 옵션은 아닙니다.

외부 이미지 도메인 설정

외부 이미지 URL을 사용하는 경우, Next.js가 해당 이미지를 최적화할 수 있도록 next.config.ts에 프로토콜·호스트·경로를 구체적으로 허용합니다.

next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
        pathname: '/**',
      },
      {
        protocol: 'https',
        hostname: 'www.example.com',
        pathname: '/images/**',
      },
    ],
  },
};

export default nextConfig;

이 예제는 Unsplash의 모든 경로를 허용하고 search를 생략해 URL의 쿼리도 허용합니다. 실제 서비스에서는 필요한 출처 범위로 제한합니다. next.config.ts를 수정한 후에는 개발 서버를 다시 시작합니다.


이미지 최적화를 위한 추가 팁

  • 적절한 이미지 크기: 이미지를 업로드하기 전에 사용될 최대 크기에 맞춰 적절히 리사이즈하는 것이 좋습니다. next/image가 반응형 이미지를 처리하지만, 너무 큰 원본 이미지를 업로드하는 것은 여전히 비효율적일 수 있습니다.
  • 파일 형식 선택
    • JPEG: 사진과 같이 색상이 풍부한 이미지에 적합하며, 손실 압축을 통해 파일 크기를 줄일 수 있습니다.
    • PNG: 투명도가 필요한 이미지나 로고, 아이콘 등 색상 수가 적고 선명도가 중요한 이미지에 적합합니다. (비손실 압축)
    • WebP / AVIF: 최신 이미지 형식으로 JPEG나 PNG보다 효율적인 압축을 제공할 수 있습니다. 기본 형식 목록은 WebP이며 브라우저의 Accept와 원본 조건에 따라 적용됩니다. AVIF를 함께 사용하려면 next.config의 images.formats를 ['image/avif', 'image/webp']처럼 설정합니다.
    • SVG: 로고, 아이콘 등 벡터 기반의 그래픽에 적합합니다. 벡터 도형은 확대해도 선명하지만 복잡도에 따라 파일 크기는 달라집니다. 일반 <img>나 <Image unoptimized>로 표시할 수 있습니다. SVG를 React 컴포넌트로 import하려면 별도 로더 설정이 필요합니다.
  • Placeholder 사용: placeholder="blur" 또는 placeholder="empty"를 사용하여 이미지가 로딩되는 동안 사용자 경험을 개선합니다. LCP 이미지가 명확하다면 preload와 함께 사용할 수 있지만, 작은 Base64 placeholder가 실제 전송 비용을 늘리지 않는지도 확인합니다.
  • 캐싱 전략: CDN(Content Delivery Network)을 사용하여 사용자에게 물리적으로 가장 가까운 서버에서 이미지를 제공하여 로딩 속도를 단축합니다. Next.js는 기본적으로 Vercel에 배포할 때 내장된 이미지 최적화 서비스를 활용합니다.
  • Lazy Loading 활용: 기본값은 lazy입니다. 브라우저가 뷰포트 주변의 이미지를 미리 가져올 수 있으므로, 화면 밖이라는 이유만으로 아직 요청되지 않았다고 단정하지 않습니다.

이미지 최적화는 단순히 이미지를 웹 페이지에 넣는 것을 넘어, 사용자 경험과 웹 성능을 극대화하기 위한 필수적인 과정입니다.

Next.js의 next/image 컴포넌트를 올바르게 활용하면 개발자가 복잡한 최적화 기법을 직접 구현할 필요 없이 뛰어난 이미지 성능을 얻을 수 있습니다.

외부 API와의 통합

이전 페이지

폰트 최적화

다음 페이지

이 페이지의 목차

왜 이미지를 최적화해야 하나요?next/image 컴포넌트 사용하기next/image 설치기본 사용법<Image> 컴포넌트의 주요 속성외부 이미지 도메인 설정이미지 최적화를 위한 추가 팁