frontend

Next.js Image 컴포넌트로 이미지를 최적화하는 방법 — WebP 자동 변환과 lazy loading

img 태그 대신 Next.js Image 컴포넌트를 쓰면 WebP 자동 변환, lazy loading, 레이아웃 시프트 방지가 한 번에 해결된다. width/height 지정, fill 모드, 외부 이미지 도메인 허용까지 정리했다.

Next.jsImage최적화WebP성능
Next.js Image 컴포넌트 코드 예시와 Chrome DevTools Network 탭에서 WebP 포맷으로 이미지가 로드되는 화면
  • ·Next.js Image 컴포넌트: 자동 WebP/AVIF 변환, lazy loading, srcset 자동 생성
  • ·CLS(Cumulative Layout Shift): width/height 미지정 시 이미지 로드 전후 레이아웃 이동 발생
  • ·fill 모드: 부모 컨테이너 크기를 채우는 방식, 부모에 position: relative 필요
  • ·remotePatterns: next.config에서 외부 이미지 도메인을 명시적으로 허용해야 함
블로그를 처음 만들 때 img 태그를 그대로 쓰다가 Lighthouse에서 이미지 최적화 경고가 잔뜩 뜨는 걸 보고 Image 컴포넌트로 전환했다. WebP 변환과 lazy loading이 자동으로 처리되는 게 가장 체감이 컸고, CLS 점수도 개선됐다. 처음에 외부 이미지를 쓸 때 도메인 허용 설정을 몰라서 오류가 났는데, next.config.ts의 remotePatterns에 추가하니 바로 해결됐다.

Next.js Image 컴포넌트란 무엇인가

Next.js Image 컴포넌트가 img 태그보다 나은 이유

HTML의 img 태그를 그대로 쓰면 이미지 최적화는 개발자가 직접 처리해야 한다. 브라우저가 지원하는 포맷에 맞게 WebP나 AVIF로 변환하고, srcset을 추가해서 화면 크기에 맞는 이미지를 제공하고, 화면 밖 이미지는 lazy loading으로 지연 로드하는 작업이 모두 수동이다. Next.js의 Image 컴포넌트는 이 과정을 자동으로 처리한다. PNG나 JPEG 이미지를 넣어도 브라우저가 지원하면 자동으로 WebP로 변환해서 제공하고, AVIF를 지원하는 브라우저에는 AVIF로 제공한다. 화면 크기에 따라 여러 해상도의 이미지를 자동으로 생성하고 srcset으로 제공한다. 기본적으로 lazy loading이 적용되어 뷰포트에 들어올 때만 이미지를 로드한다. width와 height를 미리 지정하기 때문에 이미지가 로드되기 전에도 공간이 확보되어 레이아웃 시프트(CLS)가 발생하지 않는다. 이 모든 것이 Image 컴포넌트 하나로 해결된다. Lighthouse 성능 점수에서 이미지 관련 항목들이 자동으로 개선되는 걸 확인할 수 있다.

Next.js Image 컴포넌트의 WebP 자동 변환과 lazy loading 동작 방식

Next.js Image 컴포넌트는 내부적으로 이미지 최적화 API를 통해 동작한다. src에 지정한 이미지를 Next.js 서버가 브라우저 요청 시점에 처리해서 최적화된 포맷으로 응답한다. 브라우저가 보내는 Accept 헤더를 확인해서 AVIF, WebP, JPEG 순으로 지원 여부를 판단하고 가장 효율적인 포맷을 선택한다. 변환된 이미지는 캐시되어 같은 요청이 반복될 때 재처리 없이 즉시 응답한다. 정적 사이트 빌드(output: 'export') 환경에서는 Next.js 서버가 없기 때문에 런타임 이미지 최적화가 동작하지 않는다. 이 경우 Cloudinary나 imgix 같은 외부 이미지 CDN을 loader로 연결하거나, 빌드 전에 직접 WebP로 변환해두는 방식을 써야 한다. lazy loading은 IntersectionObserver API를 기반으로 구현된다. 뷰포트에 이미지가 진입할 때 로드가 시작되어 초기 페이지 로드 시 불필요한 네트워크 요청을 줄여준다. 페이지 최상단에 있는 히어로 이미지처럼 즉시 보여야 하는 이미지에는 priority 속성을 추가해서 lazy loading을 비활성화해야 한다.

Next.js Image 컴포넌트 사용하는 방법

Next.js Image 컴포넌트에서 width, height, fill을 설정하는 방법

Image 컴포넌트를 사용하려면 next/image에서 import한다. 로컬 이미지는 정적 import를 사용하면 width와 height를 자동으로 추론한다. 외부 URL 이미지는 width와 height를 명시적으로 지정해야 한다. 두 값이 없으면 레이아웃 시프트가 발생하고 빌드 오류가 난다. 크기를 정확히 알 수 없는 상황에서는 fill 모드를 사용할 수 있다. fill 속성을 추가하면 이미지가 부모 컨테이너를 가득 채우도록 렌더링된다. 부모 요소에 반드시 position: relative와 명시적인 너비와 높이가 있어야 한다. 썸네일, 갤러리, 배경 이미지처럼 컨테이너 크기에 맞춰 이미지를 채울 때 유용하다. fill 모드에서는 object-fit CSS를 함께 설정하는 게 좋다. object-fit: cover는 비율을 유지하면서 컨테이너를 채우고, object-fit: contain은 이미지 전체가 보이도록 맞춘다. sizes 속성을 추가하면 브라우저에게 이미지가 화면의 어느 비율을 차지하는지 힌트를 줄 수 있다. 반응형 레이아웃에서 적절한 sizes를 설정하면 불필요하게 큰 이미지를 내려받는 것을 방지할 수 있다.

import Image from 'next/image'
import thumbnail from '@/public/images/post.jpg'

// 로컬 이미지 (크기 자동 추론)
<Image src={thumbnail} alt="포스트 썸네일" />

// 외부 URL (width, height 필수)
<Image
  src="https://example.com/image.jpg"
  alt="외부 이미지"
  width={1200}
  height={630}
/>

// fill 모드
<div style={{ position: 'relative', width: '100%', height: '300px' }}>
  <Image
    src="/images/hero.jpg"
    alt="히어로 이미지"
    fill
    style={{ objectFit: 'cover' }}
    sizes="100vw"
    priority
  />
</div>

Next.js Image 컴포넌트에서 외부 이미지 도메인을 허용하는 방법

Next.js Image 컴포넌트에서 외부 URL 이미지를 쓰려면 next.config.ts에서 해당 도메인을 명시적으로 허용해야 한다. 허용하지 않은 외부 도메인의 이미지를 쓰면 Invalid src prop 오류가 발생한다. images.remotePatterns 배열에 허용할 도메인 패턴을 추가하면 된다. protocol, hostname, port, pathname을 조합해서 허용 범위를 지정한다. hostname에 와일드카드 패턴을 사용하면 서브도메인까지 포함해서 허용할 수 있다. 구형 방식인 images.domains도 있는데, 이 방식은 패스 제한이 없어 보안이 더 취약하다. Next.js 공식 문서에서는 remotePatterns 사용을 권장한다. 외부 이미지 URL의 도메인을 확인하고 해당 도메인만 허용 목록에 추가하는 것이 좋다. 와일드카드로 모든 도메인을 허용하면 악의적인 URL로 이미지 최적화 API가 남용될 수 있다. 설정을 변경한 후에는 Next.js 개발 서버를 재시작해야 적용된다. 실제로 처음 외부 이미지를 쓸 때 이 설정을 몰라서 오류 메시지에 안내된 대로 remotePatterns를 추가하니 바로 해결됐다.

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

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
      {
        protocol: 'https',
        hostname: '**.cloudinary.com',
      },
    ],
  },
}

export default config

Next.js Image 최적화 실용 설정

Next.js Image 컴포넌트에서 priority와 placeholder를 설정하는 방법

Image 컴포넌트는 기본적으로 lazy loading이 적용되어 뷰포트에 이미지가 진입할 때 로드를 시작한다. 페이지를 열자마자 보이는 히어로 이미지나 상단 썸네일처럼 즉시 표시되어야 하는 이미지에는 priority 속성을 추가해야 한다. priority가 있으면 lazy loading이 비활성화되고 preload 링크가 HTML head에 추가되어 이미지를 우선적으로 불러온다. LCP(Largest Contentful Paint) 점수에 영향을 주는 이미지에 priority를 달면 Lighthouse 성능 점수가 개선된다. placeholder 속성을 활용하면 이미지가 로드되는 동안 표시할 내용을 지정할 수 있다. blur 값을 사용하면 이미지가 로드되기 전에 흐릿한 미리보기가 표시된다. 로컬 정적 이미지는 blurDataURL을 자동으로 생성해준다. 외부 이미지나 동적 이미지는 blurDataURL에 직접 Base64 인코딩된 작은 이미지를 제공해야 한다. 이 작은 이미지는 수십 픽셀짜리 저화질 JPEG를 Base64로 변환한 것으로 충분하다. 블로그 포스트 목록에서 썸네일 이미지에 blur placeholder를 설정하면 로딩 중에 빈 공간이 생기지 않고 자연스러운 화면 전환 효과를 줄 수 있다.

Next.js 정적 사이트에서 Image 컴포넌트를 최적화하는 방법

Next.js를 output: 'export' 설정으로 정적 사이트로 빌드하면 런타임에 서버가 없어서 Image 컴포넌트의 자동 이미지 최적화 기능이 동작하지 않는다. 이 상태에서 Image 컴포넌트를 쓰면 빌드 시 경고가 나거나 이미지 최적화 없이 원본 이미지가 그대로 제공된다. 해결 방법은 여러 가지다. 첫 번째는 next.config.ts에서 images.unoptimized: true로 설정하는 것이다. 최적화 없이 원본 이미지를 그대로 쓰겠다는 의미다. 이미지 최적화 혜택은 포기하지만 빌드 오류를 해결할 수 있다. 두 번째는 외부 이미지 최적화 CDN을 custom loader로 연결하는 것이다. Cloudinary나 imgix를 loader로 설정하면 정적 사이트에서도 이미지 최적화 기능을 쓸 수 있다. 세 번째는 배포 전에 이미지를 미리 WebP로 변환해서 저장해두는 것이다. sharp나 imagemin 같은 도구로 빌드 전 이미지를 일괄 변환하면 정적 사이트에서도 최적화된 이미지를 제공할 수 있다. 블로그처럼 이미지가 많지 않은 경우라면 세 번째 방법이 외부 서비스 의존 없이 단순하게 해결되는 방법이다.

자주 묻는 질문

Image 컴포넌트에서 fill을 쓸 때 이미지가 안 보이거나 레이아웃이 깨지는 이유가 무엇인가요?+

부모 요소에 position: relative가 없거나 높이가 지정되지 않은 경우가 원인입니다. fill 모드에서 Image는 position: absolute로 렌더링되기 때문에 부모에 position: relative와 명시적인 높이가 반드시 있어야 합니다.

외부 이미지를 쓸 때 Invalid src prop 오류가 나면 어떻게 하나요?+

next.config.ts의 images.remotePatterns에 해당 도메인을 추가해야 합니다. 추가 후 Next.js 개발 서버를 재시작해야 적용됩니다. 오류 메시지에 추가해야 할 설정 예시가 안내되므로 메시지를 참고해서 그대로 추가하면 됩니다.

정적 사이트(output: export)에서 Image 컴포넌트가 동작하지 않는 이유가 무엇인가요?+

output: 'export'로 빌드하면 Next.js 서버가 없어서 런타임 이미지 최적화 API가 동작하지 않습니다. next.config.ts에 images: { unoptimized: true }를 설정하면 최적화 없이 이미지를 쓸 수 있습니다.

LCP 이미지에 priority를 꼭 달아야 하나요?+

네, 필수입니다. LCP 요소가 되는 이미지에 priority가 없으면 lazy loading으로 인해 이미지 로드가 지연되어 LCP 점수가 낮아집니다. 페이지에서 가장 먼저 보이는 큰 이미지에는 반드시 priority 속성을 추가하세요.

관련 글