Next.js <Image />는 실제로 무엇을 최적화할까

_sw_·2026년 3월 26일
post-thumbnail

이전 업무에서 일부 페이지를 Next.js 기반으로 마이그레이션하면서, 기존에 사용하던 <img> 태그를 <Image /> 컴포넌트로 교체한 경험이 있었다.

Next.js는 이미지 처리 과정에서 기본적으로 몇 가지 최적화 기능을 제공한다.

  • Lazy Loading
  • 포맷 변환 (원본 → WebP / AVIF)
  • Responsive Image 생성

마이그레이션 당시에는 이미지가 화면에서 언제 노출되는지를 기준으로 우선순위를 다르게 적용했다.

첫 화면에 바로 보이는 배너 이미지는 priority를 사용해 먼저 요청되도록 했고, 리스트 내부 썸네일은 기본 lazy loading 동작을 유지해 초기 렌더링 비용을 줄이고자 했다.

이번에는 Next.js가 제공하는 이미지 최적화 기능을 하나씩 정리하면서, 어떤 요소가 실제 체감 성능에 가장 큰 영향을 주는지도 함께 확인해보려고 한다.


성능 비교해보기

총 4가지 경우를 두고, 같은 이미지를 렌더링이 어떻게 이뤄지고 있는지 확인해보았다.
앞으로 정리할 테스트는 아래 링크를 통해서 직접 볼 수 있다.
https://test-image-improvement.vercel.app/

A. 기본 <img/>

  • viewport 포함 여부와 관계없이 모든 이미지 리소스가 초기 렌더링 시점에 동시에 요청된다.
  • 이미지의 타입이 jpeg로 원본 이미지 포멧을 따라간다.

B. LazyLoading 적용

A 와 동일한 리소스를 활용하고 대신 img 태그의 loading 속성을 lazy 로 설정하여 Lazy Loading 되도록 설정한 후 다시 확인해보았다.

<img src="image.jpg" alt="..." loading="lazy" />
<iframe src="video-player.html" title="..." loading="lazy"></iframe>

초기에는 렌더링시에 바로 노출되는 이미지들만 로드한다. 이후 스크롤을 내려 하단에 위치한 이미지 리소스가 필요한 경우에 로드되는 것을 확인할 수 있다.

  • 초기 리소스 로드, 이후 Lazy Load 되는 리소스가 로드되는 두가지 시점이 타임라인에 찍혀있는 것을 볼 수 있다.
  • 타입은 여전히 jpeg 원본을 활용하고 있다. 여기서 /redirect로 찍힌 요청의 경우 같은 이미지로 테스트를 하고 있기 떄문에 같은 이미지라도 서로 다른 요청으로 인식 시키기 위해서 들어간 실험적인 요소다.

C. Next.js Image 컴포넌트 기본 활용

  • 현재 테스트에서는 viewport 안에 있는 이미지들이 먼저 요청되는 모습을 볼 수 있었다.
  • 이미지 타입이 jpeg 원본 이미지 포멧에서 avif 이미지 포멧으로 변경되었다.
  • <Image /> 역시 기본적으로 lazy loading이 적용되며, viewport 밖 이미지는 즉시 요청되지 않는다.

Next.js 의 <Image />는 원본 파일 자체를 직접 변경하는 방식이 아니라, 요청 시점에 최적화된 이미지를 별도 endpoint를 통해 전달하는 구조로 동작한다.

실제 Network 탭을 보면 원본 이미지 URL이 그대로 사용되지 않고, _next/image 경로를 통해 다시 요청되는 것을 확인할 수 있다.

w(width), q(quality) 같은 쿼리도 추가되어서 이미지 최적화가 적용된 것을 볼 수 있다.


D. Next.js Image 컴포넌트의 priority 활용

  • priority가 적용된 이미지는 lazy loading이 비활성화되고 preload 대상으로 먼저 요청된다.
  • 이미지 포맷이나 품질 자체는 달라지지 않지만, 초기 렌더링 시점에 요청되는 리소스가 앞당겨진다. 상단 이미지(C)와 비교했을 때 이미지 사이즈가 커진 것을 볼 수 있었다.
  • preload 시점과 선택된 width 후보에 따라 일부 이미지의 초기 전송량이 더 크게 나타날 수 있었고, 실제로는 LCP도 오히려 길어지는 결과가 나타났다.

E. Next.js Image 컴포넌트의 size 적용해주기

<Image
  src={...}
  sizes="(max-width: 768px) 100vw, 33vw"
/>
- C케이스와 비교했을 때, 요청 수는 동일했지만 전송량이 크게 감소했다. - sizes를 지정하면 브라우저가 srcset 후보 중 현재 레이아웃에 더 적절한 크기를 선택할 수 있다. - 같은 이미지라도 더 작은 width 후보를 선택하면서 초기 전송 비용 차이가 크게 나타났다.

정리

이번 최적화 케이스들을 비교하면서 Next.js 공식 문서도 함께 살펴보았는데, 이번에 확인한 옵션 외에도 이미지 품질, 크기, placeholder 등 다양한 설정이 제공되고 있었다.

케이스초기 요청 수전송량LCP
A. <img> eager24개4,588 kB28.47s
B. <img> lazy8개1,827 kB13.06s
C. <Image />8개1,198 kB8.44s
D. <Image priority />8개1,198 kB11.33s
E. <Image + sizes />8개378 kB3.3s

가장 먼저 체감되는 차이는 lazy loading 여부에서 나타났고, 가장 큰 전송량 감소는 sizes 설정에서 확인할 수 있었다.

<img> 태그에서도 loading="lazy"만으로 초기 요청 수를 줄일 수 있었지만, <Image />는 여기에 포맷 변환과 _next/image 기반 최적화 경로까지 함께 제공한다는 점에서 차이가 있었다.

반면 priority는 첫 화면에서 반드시 빠르게 보여야 하는 이미지에는 유효했지만, 여러 이미지에 동시에 적용할 경우 기대한 만큼 성능이 개선되지 않을 수도 있었다.
특히 이번 비교에서는 sizes 없이 priority만 적용했을 때 LCP가 오히려 길어지는 결과도 확인할 수 있었다.

결국 <Image />는 단순히 태그를 교체하는 것보다, 어떤 이미지를 어떤 크기로 먼저 보여줄지까지 함께 설계해야 효과가 커지는 기능에 가까웠다.
특히 sizes를 함께 지정했을 때 responsive image 최적화 효과가 가장 분명하게 나타나는 것을 확인할 수 있었다.

무조건 Image 컴포넌트가 좋은가..?

물론 모든 이미지에 <Image />가 필요한 것은 아니다. 이미 충분히 작은 리소스나 SVG처럼 포맷 최적화 이득이 크지 않은 경우에는 기본 <img> 태그가 더 단순한 선택이 될 수 있다.
특히 반복적으로 사용하는 정적 이미지라면, 처음부터 WebP 같은 가벼운 포맷으로 준비해 <img> 태그로 직접 사용하는 편이 오히려 불필요한 최적화 단계를 줄이는 방법이 될 수 있다.


마치며

이번 내용을 정리하면서 lazy loading과 priority를 적용할 때의 판단이 크게 잘못되지 않았다는 점을 다시 확인할 수 있었다 😩

특히 Network 타임라인으로 리소스 요청 시점을 직접 비교해보니, 어떤 최적화가 실제로 먼저 체감되는지 더 직관적으로 이해할 수 있었다.

또한, <Image />를 사용하는 경우뿐 아니라, 기본 <img> 태그를 선택하는 기준도 함께 정리할 수 있었다.


Reference

0개의 댓글