성능 최적화 17편 - 이미지 최적화로 페이지 로딩 속도 개선하기
16편에서는 JS 코드 자체를 필요한 시점에만 불러오는 코드 스플리팅을 다뤘습니다. 그런데 실제 웹페이지에서 용량을 가장 많이 차지하는 건 대개 JS가 아니라 이미지입니다. 이번 글에서는 이미지로 인한 로딩 지연을 줄이는 세 가지 방법, Lazy Loading, 포맷 최적화(WebP), 반응형 이미지(srcset)를 정리합니다.
1. 이미지가 로딩 속도에 미치는 영향
상품 목록 페이지에 상품 이미지 50개가 있다고 가정해보겠습니다. 각 이미지가 300KB라면, 페이지 진입과 동시에 브라우저는 총 15MB에 달하는 이미지를 한꺼번에 요청합니다.
[페이지 진입 즉시]
이미지 50개 x 300KB = 15MB 동시 다운로드 시작
-> 네트워크 대역폭을 이미지 요청이 전부 점유
-> 정작 화면에 보이는 상단 몇 개 이미지도 늦게 그려짐
화면에 보이지도 않는(스크롤을 내려야 보이는) 이미지까지 전부 미리 받아오는 것이 문제의 핵심입니다.
2. Lazy Loading - 보이는 시점에만 로드하기
가장 간단한 해결책은 이미지 태그에 loading="lazy" 속성을 추가하는 것입니다.
function ProductCard({ product }) {
return (
<div className="card">
<img
src={product.thumbnailUrl}
alt={product.name}
loading="lazy"
/>
<h3>{product.name}</h3>
</div>
);
}
loading="lazy"가 적용된 이미지는 브라우저 뷰포트(화면에 보이는 영역) 근처에 도달하기 전까지는 다운로드되지 않습니다. 사용자가 스크롤을 내려야 비로소 해당 이미지가 로드됩니다.
[Lazy Loading 적용 전] 페이지 진입 시 50개 이미지 동시 요청
[Lazy Loading 적용 후] 페이지 진입 시 화면에 보이는 8개만 요청, 나머지는 스크롤 시 순차 로드
단, 첫 화면(Above the Fold)에 바로 보이는 이미지에는 loading="lazy"를 붙이지 않는 것이 좋습니다. 오히려 로딩을 지연시켜 사용자가 처음 보는 화면이 늦게 그려질 수 있기 때문입니다.
3. 이미지 포맷 최적화 - WebP
같은 이미지라도 포맷에 따라 파일 크기가 크게 달라집니다. WebP는 JPEG, PNG 대비 동일한 화질에서 파일 크기가 훨씬 작은 차세대 이미지 포맷입니다.
같은 이미지 기준 용량 비교
JPEG : 300KB
PNG : 450KB
WebP : 120KB (JPEG 대비 약 60% 감소)
<picture> 태그를 사용하면, WebP를 지원하는 브라우저에는 WebP를, 지원하지 않는 구형 브라우저에는 기존 포맷을 보여주도록 폴백 처리를 할 수 있습니다.
function ProductImage({ product }) {
return (
<picture>
<source srcSet={product.thumbnailWebpUrl} type="image/webp" />
<img src={product.thumbnailJpgUrl} alt={product.name} loading="lazy" />
</picture>
);
}
브라우저는 <source> 태그를 순서대로 확인하다가, 지원 가능한 첫 번째 포맷을 사용합니다. WebP를 지원하지 않는 브라우저라면 자연스럽게 <img>의 JPEG로 대체됩니다.
4. srcset으로 화면 크기별 이미지 제공하기
모바일 화면에서는 작은 이미지로도 충분한데, PC용 고해상도 이미지를 그대로 내려받으면 불필요한 용량 낭비가 발생합니다. srcset을 사용하면 화면 크기에 맞는 이미지를 브라우저가 알아서 선택하도록 할 수 있습니다.
function ProductImage({ product }) {
return (
<img
src={product.imageUrl_800w}
srcSet={`
${product.imageUrl_400w} 400w,
${product.imageUrl_800w} 800w,
${product.imageUrl_1200w} 1200w
`}
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
alt={product.name}
loading="lazy"
/>
);
}
srcSet에 나열된 이미지들과 각각의 실제 너비(400w,800w등)를 지정합니다.sizes는 화면 너비에 따라 이미지가 실제로 얼마만큼의 폭으로 표시될지 브라우저에 알려줍니다.
브라우저는 현재 화면 크기와 기기의 화면 밀도(DPR)를 계산해서, 가장 적합한 이미지를 자동으로 선택해 다운로드합니다. 모바일 사용자는 400w 이미지만 받고, 큰 모니터 사용자는 1200w 이미지를 받는 식으로 각자에게 필요한 만큼만 전송됩니다.
5. 세 가지를 함께 적용하기
function ProductImage({ product }) {
return (
<picture>
<source
srcSet={`
${product.webp400} 400w,
${product.webp800} 800w,
${product.webp1200} 1200w
`}
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
type="image/webp"
/>
<img
src={product.jpg800}
srcSet={`
${product.jpg400} 400w,
${product.jpg800} 800w,
${product.jpg1200} 1200w
`}
sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
alt={product.name}
loading="lazy"
/>
</picture>
);
}
포맷 최적화(WebP), 화면 크기별 최적화(srcset), 필요 시점 로딩(lazy)을 함께 적용하면 세 가지 낭비 요소를 동시에 줄일 수 있습니다.
6. 레이아웃 밀림(CLS) 방지도 함께 고려하기
이미지가 늦게 로드되면서 주변 콘텐츠가 밀리는 현상(Cumulative Layout Shift)도 사용자 경험에 영향을 줍니다. width, height 속성이나 aspect-ratio를 미리 지정해서, 이미지가 로드되기 전에도 해당 영역만큼 공간을 미리 확보해두는 것이 좋습니다.
img {
aspect-ratio: 4 / 3;
width: 100%;
height: auto;
}
7. 적용 전후 비교
[최적화 전]
초기 로딩 시 이미지 요청: 50개 (15MB)
페이지 로딩 완료 시간: 4.2초
[최적화 후 - Lazy Loading + WebP + srcset]
초기 로딩 시 이미지 요청: 8개 (960KB)
페이지 로딩 완료 시간: 1.1초
8. 정리
- 화면에 보이지 않는 이미지까지 한꺼번에 받아오는 것이 페이지 로딩 지연의 주요 원인 중 하나다
loading="lazy"로 뷰포트 근처에 도달했을 때만 이미지를 로드하되, 첫 화면에 보이는 이미지는 예외로 둔다- WebP 포맷으로 동일 화질 대비 파일 크기를 크게 줄이고,
<picture>로 구형 브라우저 폴백을 함께 처리한다 srcset과sizes로 화면 크기에 맞는 이미지를 브라우저가 자동 선택하도록 해, 불필요한 고해상도 이미지 전송을 막는다
16편의 코드 스플리팅이 "코드를 필요한 만큼만 가져온다"는 원칙이었다면, 이미지 최적화는 "이미지도 필요한 시점에, 필요한 화질만큼만 가져온다"는 같은 원칙의 연장선입니다.
'성능 최적화' 카테고리의 다른 글
| 성능 최적화 STEP 19 - 트랜잭션 격리 레벨 (0) | 2026.07.15 |
|---|---|
| 성능 최적화 STEP 18 - 번들 사이즈 분석과 축소 (0) | 2026.07.14 |
| 성능 최적화 STEP 16 - 초기 로딩 속도 개선하기 (1) | 2026.07.13 |
| 성능 최적화 STEP 15 - 불필요한 리렌더링 줄이기 (0) | 2026.07.13 |
| 성능 최적화 STEP 14 - 캐시 스탬피드 방지하기 (0) | 2026.07.10 |