최근 Next.js를 시작해보면서 개발 중에
HTML의 <img> 태그가 아니라 Next에서 제공하는 <Image> 컴포넌트를 사용하라는 경고 문구를 계속 보게 됐다.
공부를 진행하면서 프론트엔드 성능 최적화에 대해 이것저것 알아보다 보니, 이 경고가 단순한 권장이 아니라는 생각이 들었다.
프론트엔드에서 성능은 사용자 경험에 큰 영향을 미친다. 사용자는 몇 초는 물론이고, 아주 짧은 지연에도 불편함을 느낀다. 그래서 최적화를 통해 최대한 쾌적한 UX를 제공하는 것이 중요하다고 느꼈다.
이런 저런 프로젝트를 진행하면서 이미지에서 소모되는 리소스가 생각보다 크다는 걸 체감하게 됐다. 텍스트는 바로 렌더링되는데 이미지는 한참 뒤에 로딩되거나, 이미지 자체 용량이 크거나, 레이아웃이 이미지 로딩 때문에 살짝 흔들리는 경우도 있었다.
이런 이미지들이 페이지 로딩 성능에 어떤 영향을 미치고,
왜 많은 사람들이 이미지 최적화에 신경 쓰는지,
그리고 Next.js에서 굳이 <Image> 컴포넌트를 권장하는 이유가 궁금해졌다.
그래서 이미지 최적화가 왜 중요한지, 그리고 Next.js의 <Image> 컴포넌트가 어떤 문제를 해결해주려는지를 정리해보고자 한다.
웹에서 이미지를 효율적으로 다루기 위해서는
이미지가 어떤 방식으로 표현되고, 어떤 기준으로 저장되는지를 이해할 필요가 있다.
이미지 최적화는 결국
이미지의 특성에 맞는 포맷과 압축 방식을 선택하는 과정이기 때문이다.
이미지는 크게 벡터 방식과 래스터 방식으로 나뉜다.
벡터 이미지는 점, 선, 곡선 등을 수식과 좌표 정보로 저장한다.
이미지를 확대하거나 축소해도 수학적으로 다시 그리기 때문에 품질이 깨지지 않는다.
대표 포맷: SVG
래스터 이미지는 픽셀 단위의 색상 정보를 저장한다.
사진처럼 디테일이 중요한 이미지에 적합하지만, 해상도가 커질수록 파일 크기도 함께 커진다.
대표 포맷: JPEG, PNG, GIF, WebP, AVIF
| 구분 | 벡터(Vector) | 래스터(Raster) |
| 표현 방식 | 수식과 좌표 | 픽셀 |
| 확대/축소 | 품질 유지 | 깨짐 발생 |
| 용량 특성 | 단순할수록 작음 | 해상도 높을수록 큼 |
| 사용처 | 로고, 아이콘, UI | 사진, 배너 |
래스터 이미지는 픽셀(pixel) 로 구성된다.
픽셀 수가 많을수록 이미지가 선명해지지만, 그만큼 용량도 커진다.
| 용어 | 의미 |
| Pixel | 색상을 가진 이미지 최소 단위 |
| PPI | 화면 픽셀 밀도 (웹) |
| DPI | 인쇄 해상도 |
👉 웹에서는 픽셀 수가 많아질수록 용량이 커진다는 점만 기억하면 충분!
이미지는 압축 방식에 따라 무손실 압축(Lossless) 과 손실 압축(Lossy) 으로 나뉜다.
대표 포맷
사용하는 상황
대표 포맷
사용하는 상황
| 포맷 | 방식 | 압축 | 투명 | 애니메이션 | 특징 |
| JPEG | 래스터 | 손실 | ❌ | ❌ | 사진에 적합 |
| PNG | 래스터 | 무손실 | ✔️ | ❌ | UI, 투명 필요 |
| GIF | 래스터 | 무손실 | ✔️ | ✔️ | 구식, 비효율 |
| WebP |
이미지 최적화는 작은 용량을 유지하면서도 좋은 품질의 이미지를 제공하기 위해 거치는 과정이다.
그렇다면 이미지를 최적화하면 어떤 점이 좋아질까?
이미지 로딩이 느린 웹사이트는 사용자에게 즉각적인 피로감을 준다.
특히 이미지가 핵심 콘텐츠인 서비스일수록, 이미지가 얼마나 빠르게 보이느냐는 매우 중요하다.
어떤 형태의 사이트든 사용자가 최대한 빠르게 이미지를 확인할 수 있도록 하는 것이 기본적인 목표가 된다.
이미지는 페이지에서 가장 용량이 큰 리소스 중 하나다.
따라서 이미지 로딩 속도는 페이지 전체 로딩 속도에 직접적인 영향을 미친다.
이미지 최적화는 단순히 이미지 하나의 문제가 아니라,
페이지 성능 전반을 개선하는 핵심 요소라고 볼 수 있다.
최적화된 이미지는 검색 엔진에도 도움이 된다.
즉, 빠르게 로드되는 웹사이트는 검색 결과에서 더 유리한 위치를 차지할 가능성이 높다.
웹 페이지에서 이미지를 로드하는 속도는 이미지 용량에 크게 영향을 받는다.
이미지 용량을 줄이면
따라서 목적과 브라우저 지원 환경에 맞는 이미지 포맷 선택이 중요하다.
WebP는 2010년 구글에서 개발한 이미지 포맷으로, 현재 웹 이미지에서 가장 많이 사용되고 있다.
특징
장점
단점
AVIF는 2017년 넷플릭스, 구글, 애플 등이 참여한 오픈 소스 비디오 코덱 AV1을 기반으로 만든 최신 포맷이다.
특징
장점
단점
이미지 최적화에서 AVIF를 써야할까? 또는 WebP만으로 충분할까? 등이 고민될 수 있다.
AVIF 사용이 적합한 경우
| 이미지 유형 | 권장 여부 | 이유 |
| 주요 히어로 이미지 | 예 | 큰 이미지일수록 압축 효과가 큼 |
| 제품 사진 | 예 | 고화질 유지 + 전송량 감소 |
| 메인 배너 / 랜딩 이미지 | 예 | LCP 개선에 유리 |
| 블로그 본문 이미지 | 경우에 따라 | WebP 대비 설정이 복잡 |
| 썸네일 | 제한적 | 압축 효과가 크지 않음 |
| 아이콘 / 로고 | 아니요 | SVG 또는 WebP 권장 |
AVIF 사용 시 권장 전략
WebP 사용이 적합한 경우
| 이미지 유형 | 권장 여부 | 이유 |
| 기존 JPEG 이미지 | 예 | 평균 25~35% 용량 절감 |
| PNG 사진 | 예 | 투명도 유지 |
| 애니메이션 GIF | 예 | GIF 대비 용량 대폭 감소 |
| 블로그 이미지 | 예 | 설정 간단 + 호환성 우수 |
| 썸네일 | 예 | JPEG 대비 효율적 |
| 아이콘 / 로고 | 경우에 따라 | 가능하면 SVG 우선 |
빠른 선택 가이드
위에서 말했듯이 브라우저마다 지원하는 이미지 포맷이 다르기 때문에 <picture> 태그를 사용하면 브라우저에 맞는 최적의 포맷을 선택적으로 제공할 수 있다.
<picture><sourcetype="image/webp"srcset="/image.webp?width=100 100w,/image.webp?width=200 200w,/image.webp?width=400 400w,/image.webp?width=800 800w"/><img
<picture><source type="image/avif" srcset="/image.avif 800w" /><source type="image/webp" srcset="/image.webp 800w"
브라우저는 위에서부터 지원 가능한 포맷을 자동 선택한다.
웹 페이지에 이미지를 넣을 때, 흔히 CSS의 background-image를 사용하는 경우가 많다.
.hero {background-image:url('/image.png');}
겉보기에는 간단하고 편리하지만, 성능 관점에서는 여러 문제가 존재한다. 특히 큰 이미지나 주요 콘텐츠 이미지라면 더욱 주의해야 한다.
background-image를 사용하면 모든 사용자에게 동일한 이미지 파일을 전달하게 되기 쉽다. 이렇게 되면 불필요하게 큰 이미지를 다운로드하게 만들 수 있다.
.hero {background-image:url('/image.png'); }
물론 예외적으로 아래 같은 경우에는 background-image가 적절할 수 있다.
하지만, 히어로 이미지, 썸네일, 배너 등 의미 있는 이미지는 <img> 사용이 권장된다.
srcset은 여러 크기의 이미지 소스들을 브라우저에게 제안하는 리스트이다. 고정된 크기의 이미지를 하나만 주는 대신, 화면 크기와 해상도에 맞는 후보를 주어 브라우저가 최적의 이미지를 고르게 한다.
<imgsrcset="/image-400.png 400w, /****//image-800.png 800w,/image-1200.png 1200w"src="/image-1200.png"alt="반응형 이미지 예시"/>
예시: 이미지가 화면에서 200px로 표시되어야 할 때:
- 1배수 기기: $200 \times 1 = 200$, 따라서 200w 이미지 선택.
- 2배수 기기: $200 \times 2 = 400$, 따라서 400w 이미지 선택.
이처럼 w 단위를 쓰면 기기 사양에 상관없이 항상 최적의 선명도를 유지할 수 있습니다.
srcset만 주면 충분할 것 같지만, 결정적인 문제가 하나 있다. 바로 브라우저의 리소스 로드 시점이다.
브라우저는 페이지를 렌더링하기 전, 즉 CSS를 읽기도 전에 이미지를 미리 다운로드하기 시작한다. 이 시점에는 이미지가 화면에서 몇 픽셀로 보일지 알 수 없다. 그래서 sizes가 없으면 브라우저는 이미지가 화면 전체 너비(100vw)를 차지한다고 가정하고 큰 이미지를 받아오곤 한다.
sizes 속성의 역할
sizes는 브라우저에게 "이 이미지는 나중에 CSS가 적용되면 대략 이 정도 크기가 될 거야"라고 미리 팁을 주는 역할을 한다.
<imgsrcset="/image-400.png 400w,/image-800.png 800w,/image-1200.png 1200w"sizes="(max-width: 768px) 100vw, 50vw"src="/image-1200.png"alt="반응형 최적화 이미지
위 코드의 동작 원리
웹 페이지에는 첫 화면에서 빠르게 로드되어야 하는 이미지가 존재한다. 이러한 이미지는 fetchpriority="high"를 지정해 브라우저에 우선적으로 다운로드해야 할 리소스임을 명시할 수 있다.
<imgsrc="hero.jpg"fetchpriority="high"alt="hero image"/>
이를 통해 브라우저는
중요도가 낮은 이미지에는 fetchpriority="low"를 지정할 수도 있다.
<img src="thumb.jpg" fetchpriority="low" alt="thumbnail" />
loading="lazy"는 뷰포트 밖에 있는 이미지의 로딩을 지연시킨다.
<imgsrc="image.jpg"loading="lazy"alt="example"/>
이 속성을 사용하면
단, 첫 화면(above the fold)에 보이는 이미지에는 사용하지 않는 것이 좋다.
LCP 이미지까지 지연 로딩하면 오히려 성능이 나빠질 수 있다.
이미지를 다운로드한 후에는 디코딩 작업이 필요하며, 이 과정은 메인 스레드에 부담을 줄 수 있다.
decoding="async"를 사용하면 브라우저가 이미지 디코딩을 메인 스레드 밖에서 처리할 수 있다.
<img src="image.jpg" decoding="async" alt="example" />
MDN에서는 화면 밖에 있는 이미지에 대해 decoding="async" 사용을 권장하고 있다.
Next.js는 애플리케이션의 속도와 Core Web Vitals(opens in a new tab)를 향상시키기 위해 다양한 내장 최적화 기능을 제공한다.
내장된 컴포넌트 중 <Image> 컴포넌트를 파헤치며, NextJs는 이미지 최적화를 어떻게 진행하고 있는지 알아보고자 한다.
<Image> 컴포넌트는 기존 HTML <img> 태그를 확장한 컴포넌트로,
이미지 성능 최적화를 자동으로 수행한다.
import Image from"next/image";<Imagesrc="/image.jpg"width={800}height={600}alt="example"/>
이 간단한 코드 한 줄로, 아래의 최적화가 적용된다.
고정 크기 이미지를 모든 디바이스에 동일하게 제공하면
일반 HTML에서는 이를 해결하기 위해 srcset과 sizes를 직접 작성해야 한다.
<imgsrcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w"sizes="(max-width: 768px) 100vw, 50vw"src="medium.jpg"/>
이미지 포맷은 용량과 품질에 큰 영향을 준다.
HTML에서는 <picture>와 source를 직접 구성해야 한다.
<picture><source type="image/avif" srcset="image.avif"><source type="image/webp" srcset="image.webp">
이미지가 늦게 로드되면, 이미지 영역이 뒤늦게 생기면서 주변 콘텐츠가 밀리는 현상이 발생한다.
→ Layout Shift (CLS)
<img width="500" height="300" style="height:auto" />
→ 별도 설정 없이 CLS 최소화
모든 이미지를 한 번에 로드하면
<img loading="lazy" />
→ 단, LCP 이미지에는 lazy loading을 사용하지 않는다.
페이지에서 가장 중요한 이미지(LCP)는 가장 먼저 로드되어야 한다.
<Imagesrc="/hero.jpg"priorityalt="hero image"/>
priority 옵션은 내부적으로
<Imagesrc="https://prod-files-secure.s3.amazonaws.com/..."width={800}height={600}alt="remote image"/>
→ 출처와 상관없이 동일한 최적화 적용
| 래스터 |
| 손실/무손실 |
| ✔️ |
| ✔️ |
| 범용 최적화 |
| AVIF | 래스터 | 손실/무손실 | ✔️ | ✔️ | 최고 압축률 |
| SVG | 벡터 | 코드 | ✔️ | ✔️ | 로고·아이콘 |
image-set()은 사용자의 디스플레이 해상도에 따라 다른 이미지를 보여주는 방식이다.
.hero {background-image:image-set(url('/image-1x.png')1x, /* 일반 모니터용 */url('/image-2x.png')2x /* 고해상도(Retina) 디스플레이용 */);}
image-set()은 해상도 대응에는 도움이 되지만
CSS background-image의 로딩 순서는 아래와 같다.
HTML 다운로드 → CSS 다운로드/파싱→ background-image 발견 → 이미지 다운로드 시작
하지만 <img> 태그의 경우
HTML 다운로드 중 이미지 URL 발견 → 즉시 이미지 요청 시작▶️ 결과적으로