개발/Next.js

Next.js의 Image 최적화

dev_jiwonpark 2025. 10. 16. 01:05

웹 서비스에서 이미지가 차지하는 비중은 정말 큰 것 같다.

현재 회사에서 참여하고 있는 프로젝트만 해도 이미지를 정말 많이 다루고 있기 때문에 이미지 관련 고민들을 항상 개발하면서 하는 것 같다.

아무리 코드 로직을 최적화 하려고 해도 이미지 하나가 수 MB만 되도 로딩 속도가 급격히 느려진다.

한창 프로젝트 개발 테스트를 하면서 5~11MB 의 이미지들 10개를 한번에 로드하려고하니 로딩 속도가 엄청 느려졌었다. 

그때 개발하면서 이거 바쁘다바빠 현대사회에 사는 유저들이 이 로딩속도를 참을 수 있을까? 하는 걱정이 됐다.

 

그래서 이런 문제를 해결하기 위해 Next.js에서 Image 컴포넌트를 제공하는데 

평소 나는 개발하면서 이 Image 컴포넌트에 대해 잘 알지 못한채 사용하는 것 같아. 

이번 기회에 Image 컴포넌트의 최적화 옵션을 공부해보려고 한다. 


Next.js 공식 홈페이지에서 보면 

Next.js의 <Image> 컴포넌트는 단순한 HTML <img> 태그를 확장한 기능으로, 이미지 최적화를 자동으로 처리해주는 강력한 도구라고 한다.

 

1. 크기 최적화 (Size Optimization)

디바이스 종류나 화면 해상도에 따라 자동으로 알맞은 크기의 이미지를 제공하며

또한 WebP 같은 현대적인 이미지 포맷을 사용하여 용량을 줄이고 로딩 속도를 개선한다.

 

2. 시각적 안정성 (Visual Stability)

이미지가 로딩되는 동안 발생할 수 있는 레이아웃 시프트(Layout Shift) 현상을 방지한다.

이는 CLS(Core Web Vitals의 한 항목)를 낮추어 페이지 안정성을 높이는 데 도움이 된다.

더보기

레이아웃 시프트란?

예를 들어 블로그 글을 읽으려고 하는데 글 중앙에 있는 광고 배너 이미지가 늦게 로드되면서 

갑자기 읽고 있던 문단이 아래로 확 밀려버리며 영역이 생기는 현상을 말한다.

 

이런 현상들은 이미지나 비디오에 크기가 미리 지정되지 않았을 때 

브라우저가 영역을 예측하지 못해 로딩 후 레이아웃이 재계산된다.

그래서 이미 렌더된 레이아웃 위에 새로운 요소가 들어와 이를 밀어내게 된다.

 

Google에서는 Core Web Vitals(웹 성능을 측정하는 핵심 지표) 중 하나로 CLS(누적 레이아웃 시프트, Cumulative Layout Shift)를 정의하는데

이건 사용자들이 페이지를 보는 동안 요소들이 얼마나 자주, 얼마나 많이 이동했는지를 수치로 계산한 값이다.

즉 값이 낮을수록 페이지가 안정적이고 사용성이 좋다는 의미이다.

 

Next.js 의 <Image> 컴포넌트는 이런 문제를 미리 막는다.

이미지의 가로 , 세로 비율을 미리 계산해 레이아웃 공간을 예약한다.

덕분에 이미지가 로드되기 전에 브라우저가 정확한 영역을 확보하고 이미지가 뒤늦게 나타나도 화면이 밀리지 않는다.

3. 빠른 페이지 로딩 (Faster Page Loads)

브라우저의 지연 로딩(Lazy Loading) 기능을 활용하여,
사용자가 실제로 스크롤해서 이미지가 화면에 나타나기 전까지는 요청을 보내지 않는다.
또한 옵션으로 blur-up placeholder(이미지 로드 전 부드러운 블러 효과)도 사용할 수 있다.

 

4. 유연한 자산 처리 (Asset Flexibility)

Next.js는 원격 서버에 저장된 이미지조차도 필요할 때 즉시 리사이징하여 제공합니다.
즉, 로컬에 있든 CDN에 있든, 이미지 출처에 상관없이 최적화된 버전을 만들어준다.

 

요약하자면 <Image>는 이미지를 그냥 보여주는 것이 아니라

성능, 안정성, 유연성을 모두 챙긴 이미지 전용 최적화 솔루션이다. 

Next.js가 자동으로 처리해주므로 개발자는 UX 개선에 더 집중할수 있게 되는 것이다!

 

다음으로 Image 컴포넌트의 props들을 정리해보고 활용해보면서 실제로 이미지 로드 현상이 개선되는지 확인해보자!


우선 필수 props 들 부터 살펴보자

Next.js의 <Image> 컴포넌트를 사용할 때는 반드시 4가지 props를 지정해야 한다.
src, alt, width, height — 또는 fill을 사용해야한다.

import Image from 'next/image'

export default function Page() {
  return (
    <div>
      <Image
        src="/profile.png"
        width={500}
        height={500}
        alt="Picture of the author"
      />
    </div>
  )
}

 

 

1. src

이미지의 경로를 지정한다.
아래 세 가지 형태 중 하나를 사용할 수 있다.

  • 프로젝트 내부의 정적 파일 경로 (예: /public/profile.png)
  • 직접 입력한 경로 문자열
  • 외부 URL (이 경우 next.config.js의 images.remotePatterns에 등록해야 함)

2. width & height

이미지의 고유 너비(px 단위)를 지정합니다.
Next.js는 이 값을 이용해 이미지가 로드되기 전 공간을 미리 확보하고,
레이아웃 시프트(Layout Shift)를 방지한다.

(단, 정적 이미지(import)나 fill 속성을 사용하는 경우에는 생략할 수 있다.)

 

3. alt

이미지의 대체 텍스트(Alternative Text)이다.

  • 스크린 리더 사용자를 위한 접근성(Accessibility)을 높이고,
  • 이미지가 로드되지 않을 때 대체 텍스트로 표시되며,
  • SEO(검색 엔진 최적화)에도 중요한 역할을 한다.

+ fill 속성 

만약 이미지의 크기를 부모 컨테이너에 가득 채우고 싶다면, width와 height 대신 fill을 사용할 수 있다. 


<Image> 컴포넌트는 필수 props 외에도 이미지 로딩 성능을 위한 다양한 옵션을 제공한다.

 

1. loader

이미지 URL을 직접 커스터마이징할 때 사용하는 함수이다.
Next.js의 기본 이미지 최적화 로직 대신, 사용자 정의 URL 생성 규칙을 적용할 수 있다.

 

2. sizes

<Image>의 반응형 로딩 성능을 결정하는 아주 중요한 속성이다.
브라우저가 어떤 크기의 이미지를 내려받을지 판단하는 기준이다.

<Image
  fill
  src="/example.png"
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
  • 100vw → 화면 너비의 100%
  • 50vw → 화면 너비의 50%
  • 33vw → 화면 너비의 33%

sizes를 정확히 지정하면 브라우저가 필요 이상으로 큰 이미지를 다운로드하지 않는다.

 

3. quality

이미지 압축 품질을 설정한다.
기본값은 75이며, 값이 높을수록 선명하지만 파일 용량이 커진다.

 

4. priority

priority={true}

해당 이미지를 페이지 로드 시 가장 먼저 불러오도록(preload) 지정한다.
Largest Contentful Paint (LCP) 최적화에 특히 중요하다.

 

5. placeholder

이미지가 로딩되는 동안 표시할 플레이스홀더(placeholder)를 지정한다.

<Image
  src="/profile.jpg"
  alt="Profile"
  width={400}
  height={400}
  placeholder="blur"
/>

 


고급 props 에 대해서 알아보자!

 

1. onLoadingComplete & onLoad

우선 두가지는 이미지가 완전히 로드되고, placeholder가 제거된 이후 실행되는 콜백이다.

'use client'

<Image onLoadingComplete={(img) => console.log(img.naturalWidth)} />

 

하지만 Next.js 14 이후에는 onLoad 사용이 권장된다..!

 

2. onError

이미지 로드에 실패했을 때 실행되는 콜백이다.
예를 들어 서버에서 이미지를 불러오지 못했을 때 fallback 이미지를 표시하는 식으로 활용할 수 있다.

<Image
  src="/profile.jpg"
  onError={() => setFallback(true)}
  alt="Profile"
/>

 

위에서 언급한 세 가지 이벤트(onLoad, onError, onLoadingComplete)는 모두 클라이언트 컴포넌트 안에서만 작동한다.

 

3. loading

이미지를 즉시 로드할지, 지연 로드할지 결정하는 속성이다.

기본값은 "lazy"로, 이미지를 화면 근처에 왔을 때 로드한다.
"eager"로 바꾸면 바로 로드하지만, 성능에는 좋지 않다.

따라서 Hero 이미지처럼 중요한 영역만 먼저 불러오고 나머지는 lazy 로딩으로 처리한다.

 

4. blurDataURL

이미지가 로드되기 전에 표시할 블러(blur) 이미지의 base64 데이터를 지정하는 속성이다.
placeholder="blur"와 함께 사용할 때만 적용된다.

<Image
  src="/photo.jpg"
  alt="sample"
  width={600}
  height={400}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSk..."
/>

 

5. unoptimized

true인 경우, 품질, 크기 또는 형식을 변경하지 않고 원본 이미지를 그대로 제공한다.

 


그래서 과연 <Image> 컴포넌트를 활용해 이미지를 최적화할 수 있는지 확인해보자!

페이지를 처음 로드하고 전체 이미지가 로드완료되는 시점까지 측정해보았다.

 

우선 <img> 태그로 11MB 의 이미지를 로드해보았다.

       <img
            src={SRC}
            alt="test-img"
            style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
          />

우선 제일 먼저 눈의 띄는 LCP 부터 보면 3.20s 라고 나와있다. 

이는 

 

  • 브라우저가 네트워크에서 이미지 파일을 요청함
  • JPEG 11MB 다운로드 & 디코딩
  • 렌더링 후 화면에 표시
  • 그 시점이 3.20초 라는 것이다.

그리고 Range가 0ms - 3.43s 로 나와있다.

System
(993 ms)
브라우저 엔진 자체에서 이미지 디코딩, 압축 해제, 디스크 I/O 같은 시스템 단위 작업에 쓴 시간
Rendering
(28 ms)
브라우저가 DOM과 CSS를 계산해서 레이아웃(위치, 크기 등)을 확정하는 데 걸린 시간
Scripting
(11 ms)
JS 실행에 걸린 시간
Painting
(1 ms)
실제 픽셀을 화면에 그려주는 과정
Loading
(1 ms)
네트워크로 자원을 요청하고 응답받은 네트워크 대기 시간
Total
(3,432 ms)
위의 모든 단계를 포함한 전체 구간의 길이

 

 

이미지를 요청하고, 브라우저가 11MB짜리 JPEG를 디코딩하고, 메모리에 올려서 그리는 데 총 3.43초가 걸렸다는 뜻이다.

 

그러면 <Image> 컴포넌트를 위 옵션과 함께 활용한다면 11MB 의 이미지가 얼마나 최적화되는지 한번 확인해보자

          <Image
            src={SRC}
            alt="next-image"
            width={1600}   
            height={1200}
            loading="eager"
            style={{ width: '100%', height: '100%', objectFit: 'cover' }}
            placeholder="blur"
          />

 

우선 LCP는 3.20초 에서 1.78초로 단축되었다.

 

항목 의미 변화 이유

Range
(1.95s)
<Image>가 WebP/AVIF 등 최적 포맷으로 자동 변환해 파일 용량이 확 줄었음
System
(11ms)
JPEG → WebP로 내려와서 브라우저 디코딩 부담이 거의 없음
Rendering
(27ms)
동일함 (이건 이미지 크기보다는 레이아웃 구조 영향)
Scripting
(10ms)
Next.js 내부 스크립트 초기화 정도
Painting
(1ms)
거의 즉시 (이미지 디코딩이 끝났기 때문)
Loading
(0ms)
이미지가 이미 브라우저 캐시에 있거나, 최적화 CDN으로 빠르게 내려옴
Total
(1.947s)
<img> 대비 약 2초 절약!

 

같은 11MB 이미지라도 일반 <img>는 전체 파일을 그대로 다운로드하고 디코딩하는 데 3초 이상이 걸렸지만

Next.js <Image>는 자동 리사이즈 + 포맷 최적화(WebP/AVIF) 를 통해 1.9초 이내에 완전히 렌더링됐다..!

 

특히 LCP(Largest Contentful Paint) 지표가 3.20s → 1.78s로 개선된 건 아주 큰 변화다.
이는 단순히 수치상 빠르다는것을 넘어서

사용자가 이미지가 떴다!하고 느끼는 체감 속도와 SEO 점수에도 직접적인 영향을 주기 때문이다.

 


 

결국, 이미지 하나를 어떻게 불러오느냐에 따라 웹 성능과 사용자 경험이 이렇게 크게 달라질 수 있다는 걸 몸소 느꼈다.

그동안 <Image> 컴포넌트를 단순히 “Next.js가 권장하니까 쓰는 태그” 정도로만 생각했는데,
이번 실습을 통해 그것이 단순한 편의 기능이 아니라 실제 성능 개선의 핵심 도구라는 걸 체감했다.

앞으로는 이미지 하나를 넣더라도 “어떻게 보여줄까”보다 “어떻게 로드할까”를 먼저 고민해야겠다는 생각이 들었다.

 

 

 

참고

https://nextjs.org/docs/app/getting-started/images

 

Getting Started: Image Optimization | Next.js

Learn how to optimize images in Next.js

nextjs.org

 

https://nextjs-ko.org/docs/app/building-your-application/optimizing/images

 

Image Optimization – Nextjs 한글 문서

Optimize your images with the built-in `next/image` component.

nextjs-ko.org

https://tech.kakaoent.com/front-end/2022/220714-next-image/

 

Next/Image를 활용한 이미지 최적화 | 카카오엔터테인먼트 테크블로그

조지영(esme) 무언갈 빠르게 좋아합니다. 그래서 변화가 빠른 FE 개발이 적성에 잘 맞습니다.

tech.kakaoent.com