<img> 최적화 — loading, decoding, srcset, sizes 실제로 어떻게 쓰나
이미지 최적화에서 자주 보이는 loading, decoding, srcset, sizes 속성이 실제로 무슨 역할을 하는지, 언제 쓰고 언제 안 써도 되는지 정리했다.
<img> 최적화
이미지는 대부분의 웹 페이지에서 가장 큰 리소스다. <img> 태그에 붙일 수 있는 속성들이 실제로 무슨 역할을 하는지 이해하면 LCP를 크게 개선할 수 있다.
loading — 언제 다운로드할 것인가
<img src="photo.jpg" loading="lazy" alt="사진">| 값 | 동작 |
|---|---|
eager (기본값) | 페이지 로드 즉시 다운로드 |
lazy | 뷰포트 근처에 도달할 때 다운로드 |
loading="lazy"는 스크롤 없이 보이지 않는 이미지(fold 아래)에 적용한다. 첫 화면(Above the fold)에 있는 이미지에 lazy를 쓰면 오히려 LCP가 나빠진다.
<!-- 첫 화면 이미지 — eager(기본값) 또는 명시하지 않음 -->
<img src="hero.jpg" alt="히어로 이미지">
<!-- 스크롤해야 보이는 이미지 -->
<img src="thumbnail.jpg" loading="lazy" alt="썸네일">decoding — 어떻게 디코딩할 것인가
<img src="photo.jpg" decoding="async" alt="사진">| 값 | 동작 |
|---|---|
sync | 메인 스레드에서 동기 디코딩 (렌더링 블로킹) |
async | 별도 스레드에서 비동기 디코딩 |
auto (기본값) | 브라우저가 판단 |
decoding="async"는 이미지 디코딩이 메인 스레드를 차단하지 않게 한다. 다만 히어로 이미지처럼 LCP에 직접 영향을 주는 이미지에 async를 쓰면 이미지가 더 늦게 표시될 수 있다.
실용적 가이드:
- 히어로 이미지 →
decoding="sync"또는 생략 - 그 외 대부분 →
decoding="async"
fetchpriority — 다운로드 우선순위
loading과 decoding보다 더 직접적으로 LCP를 개선한다.
<!-- LCP 이미지에 높은 우선순위 부여 -->
<img src="hero.jpg" fetchpriority="high" alt="히어로">
<!-- 중요하지 않은 이미지 -->
<img src="decoration.jpg" fetchpriority="low" alt="">브라우저는 기본적으로 뷰포트 내 이미지를 high로 처리하지만, fetchpriority="high"를 명시하면 CSS 파싱 전에도 미리 다운로드를 시작한다.
srcset — 해상도별 이미지 제공
같은 이미지를 여러 해상도로 제공해서 디바이스에 맞는 것을 선택하게 한다.
픽셀 밀도 기반 (간단한 경우)
<img
src="photo.jpg"
srcset="photo.jpg 1x, photo@2x.jpg 2x, photo@3x.jpg 3x"
alt="사진"
>Retina(2x) 디바이스에서는 photo@2x.jpg를 다운로드한다.
너비 기반 (반응형)
<img
src="photo-800.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w
"
sizes="(max-width: 768px) 100vw, 50vw"
alt="사진"
>400w는 "이 이미지 파일의 실제 너비는 400px"라는 힌트다. 브라우저가 현재 뷰포트 너비, 픽셀 밀도, sizes 값을 조합해 가장 적합한 이미지를 선택한다.
sizes — 이미지가 실제로 차지하는 너비
srcset에 너비 디스크립터(w)를 쓸 때 반드시 함께 사용한다. CSS가 로드되기 전에 브라우저가 어떤 이미지를 다운로드할지 결정해야 하기 때문에 명시적으로 알려줘야 한다.
<img
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
sizes="
(max-width: 480px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
alt="사진"
>sizes는 CSS 미디어 쿼리와 동일한 문법이다. 첫 번째로 매칭되는 조건의 크기를 사용하고, 마지막 값은 기본값이다.
브라우저가 계산하는 방식:
현재 뷰포트 = 600px
→ sizes에서 (max-width: 1024px) 50vw 매칭
→ 필요한 이미지 너비 = 600 * 0.5 = 300px
→ 픽셀 밀도 2x이면 600px 필요
→ srcset에서 800w 선택picture — 포맷 분기와 아트 디렉션
<!-- 포맷 분기: WebP를 지원하면 WebP, 아니면 JPEG -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="사진">
</picture>
<!-- 아트 디렉션: 모바일에서는 다른 구도의 이미지 -->
<picture>
<source media="(max-width: 768px)" srcset="photo-portrait.jpg">
<img src="photo-landscape.jpg" alt="사진">
</picture><picture>는 <img>의 대체재가 아니다. <img>를 포함해야 하고, <img>에 alt와 기본 src를 붙인다.
실용적 가이드 요약
<!-- 히어로 / LCP 이미지 -->
<img
src="hero.jpg"
srcset="hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="100vw"
fetchpriority="high"
decoding="sync"
alt="히어로 이미지"
>
<!-- 일반 콘텐츠 이미지 -->
<img
src="content.jpg"
srcset="content-400.jpg 400w, content-800.jpg 800w"
sizes="(max-width: 768px) 100vw, 50vw"
loading="lazy"
decoding="async"
alt="콘텐츠 설명"
>
<!-- 장식 이미지 -->
<img src="decoration.jpg" loading="lazy" fetchpriority="low" alt="">alt=""는 틀린 게 아니다. 장식 이미지처럼 스크린 리더가 읽을 필요 없는 이미지는 빈 문자열을 쓴다. alt 자체를 생략하면 파일명을 읽는다.
width / height는 항상 명시
<img src="photo.jpg" width="800" height="600" loading="lazy" alt="사진">width와 height를 명시하면 브라우저가 이미지 다운로드 전에 공간을 예약해서 CLS(Cumulative Layout Shift)를 방지한다. CSS로 크기를 조정하더라도 비율 힌트로 활용된다.
Related Posts
같이 읽으면 좋은 글
Web Vitals — LCP·CLS·INP 측정하고 개선하기
Google이 정의한 Core Web Vitals 세 가지, LCP·CLS·INP가 각각 무엇을 측정하는지, 어떻게 확인하고 어디서 개선할 수 있는지 정리했다.
contain과 content-visibility — 브라우저가 렌더링을 건너뛰는 방법
CSS contain과 content-visibility 속성이 브라우저에게 렌더링 범위를 제한하는 힌트를 주어 성능을 높이는 원리와 실무 적용 방법을 정리했다.
Critical Rendering Path — HTML 파싱부터 화면에 픽셀이 찍히기까지
브라우저가 HTML을 받아서 화면에 그리기까지 일어나는 일들 — DOM, CSSOM, Render Tree, Layout, Paint, Composite 단계를 순서대로 정리했다.