이미지 최적화의 출발점은 포맷 선택입니다. 2026년 현재 웹 이미지 시장은 사실상 세 가지 포맷이 지배하고 있습니다.
JPEG: 30년 넘은 웹의 기본값
JPEG는 1992년부터 지금까지 웹의 기본 이미지 포맷입니다. 2025 Web Almanac 기준으로 LCP 이미지의 57%가 여전히 JPEG이고요. 모든 브라우저, 모든 디바이스에서 100% 지원되고 인코딩·디코딩이 빠르다는 장점이 있습니다. 다만 압축 효율은 확실히 떨어지고, 투명도 지원이 안 되며, HDR 색공간도 사용 불가입니다.
WebP: 이제는 사실상 표준
구글이 2010년에 발표한 WebP는 VP8 비디오 코덱 기반 압축을 씁니다. JPEG 대비 25~35% 더 작은 파일이면서 화질은 동등한 수준이에요. 손실·무손실 압축, 투명도, 애니메이션까지 다 됩니다. 2026년 기준 글로벌 브라우저 지원율 약 97%, 웹 이미지 요청의 40% 이상이 이미 WebP로 제공되고 있으니, "차세대"라기보다는 이제 사실상 표준이라고 봐야겠죠.
AVIF: 최강의 압축률, 그러나 함정이 있다
AOMedia가 2019년에 공개한 AVIF는 AV1 비디오 코덱 기반 이미지 포맷입니다. 압축률이 진짜 인상적인데, JPEG 대비 50% 이상, WebP 대비 20~30% 더 작습니다. 거기에 HDR(10비트, 12비트 색심도)까지 지원해서 고품질 히어로 이미지에 특히 좋습니다.
2026년 3월 기준 글로벌 브라우저 지원율은 93~95%. Chrome 85+, Firefox 93+, Safari 16+, Edge 121+ 전부 네이티브 지원합니다. 그런데 재밌는 건, 실제 웹에서 AVIF가 사용되는 비율은 아직 1% 미만이라는 겁니다.
이 간극이 바로 기회입니다. 지금 AVIF를 도입하면 경쟁 사이트 대비 확실한 성능 우위를 가져갈 수 있다는 뜻이니까요.
포맷별 핵심 비교
| 항목 | JPEG | WebP | AVIF |
| 압축률 (JPEG 대비) | 기준 | 25~35% ↓ | 50%+ ↓ |
| 브라우저 지원 (2026) | 100% | ~97% | ~93~95% |
| 투명도 (알파) | ✗ | ✓ | ✓ |
| HDR/광색역 | ✗ | ✗ | ✓ |
| 애니메이션 | ✗ | ✓ | ✓ |
| 인코딩 속도 | 빠름 | 보통 | 느림 |
| 디코딩 속도 | 빠름 | 빠름 | 느림 (주의) |
| 권장 품질 설정 | 75~85 | 75~85 | 60~75 |
2026년 황금 규칙: AVIF → WebP → JPEG 순서로 제공하되, AVIF의 디코딩 비용은 반드시 체크하세요. (이 부분은 뒤에서 자세히 다룹니다.)
picture 태그로 포맷 자동 분기 처리하기
차세대 포맷 도입 시 가장 흔한 실수가 뭘까요? 특정 브라우저 사용자가 이미지를 아예 못 보게 되는 겁니다. <picture>와 <source>를 조합하면, 브라우저가 알아서 지원 가능한 최적 포맷을 골라갑니다.
<picture>
<!-- 1순위: AVIF (최고 압축률) -->
<source
srcset="hero.avif"
type="image/avif"
>
<!-- 2순위: WebP (높은 호환성) -->
<source
srcset="hero.webp"
type="image/webp"
>
<!-- 3순위: JPEG (폴백) -->
<img
src="hero.jpg"
alt="메인 히어로 배너"
width="1200"
height="600"
>
</picture>
동작 방식은 단순합니다. 브라우저가 위에서부터 <source>를 하나씩 확인하고, AVIF를 지원하면 AVIF를, 안 되면 WebP를, 그것마저 안 되면 <img>의 JPEG를 가져옵니다. 네트워크 요청은 딱 하나만 발생하고요.
picture 태그 쓸 때 꼭 지켜야 할 것들
<img> 태그는 반드시 포함. <picture> 안에 <img>가 없으면 아무것도 렌더링되지 않습니다.
width와 height는 <img>에 지정. 브라우저가 로딩 전에 공간을 확보해 CLS를 방지합니다.
alt 텍스트도 <img>에. 접근성과 SEO 둘 다를 위해 필수입니다.
- CSS
aspect-ratio를 같이 쓰면 레이아웃 안정성을 한층 더 높일 수 있습니다.
/* CSS로 레이아웃 안정성 강화 */
img {
aspect-ratio: attr(width) / attr(height);
width: 100%;
height: auto;
object-fit: cover;
}
반응형 이미지: srcset과 sizes로 디바이스별 최적 이미지 보내기
포맷만큼 중요한 게 크기입니다. 데스크톱용 1920px 히어로 이미지를 375px 모바일 화면에 그대로 보내는 건, 솔직히 대역폭 낭비이자 LCP 저하의 직접적인 원인입니다.
srcset의 w 디스크립터
srcset은 브라우저한테 "이 이미지들 중 하나 골라"라고 후보군을 알려주는 속성입니다. w 디스크립터는 각 후보의 실제 픽셀 너비를 명시하고요.
<img
srcset="
hero-480.webp 480w,
hero-768.webp 768w,
hero-1200.webp 1200w,
hero-1920.webp 1920w
"
sizes="
(max-width: 480px) 100vw,
(max-width: 768px) 100vw,
(max-width: 1200px) 80vw,
1200px
"
src="hero-1200.webp"
alt="히어로 배너"
width="1920"
height="960"
>
sizes 속성 누락: 가장 흔하면서도 치명적인 실수
이 부분이 한국어 자료에서 거의 다뤄지지 않는 핵심입니다.
srcset에 w 디스크립터를 쓰면서 sizes를 빼먹으면, 브라우저는 이미지가 뷰포트 전체 너비(100vw)를 차지한다고 가정해 버립니다. 사이드바 옆에 놓인 600px짜리 이미지인데도 1920px 파일을 다운로드한다는 거예요. 필요량의 3배가 넘는 이미지를 받는 셈이죠.
제 경험상 실제 프로젝트에서 이 실수가 정말 흔합니다. Lighthouse에서 "Properly size images" 경고가 뜨는 사이트의 상당수가 바로 이 문제입니다.
<!-- ❌ 잘못된 예: sizes 누락 -->
<img
srcset="photo-480.webp 480w, photo-960.webp 960w, photo-1920.webp 1920w"
src="photo-960.webp"
alt="상품 사진"
>
<!-- 브라우저가 100vw로 가정 → 불필요한 대용량 이미지 다운로드 -->
<!-- ✅ 올바른 예: sizes 명시 -->
<img
srcset="photo-480.webp 480w, photo-960.webp 960w, photo-1920.webp 1920w"
sizes="(max-width: 768px) 100vw, 600px"
src="photo-960.webp"
alt="상품 사진"
width="960"
height="640"
>
<!-- 768px 이하에서는 뷰포트 전체, 그 이상에서는 600px 기준으로 이미지 선택 -->
picture + srcset 조합: 포맷과 크기 동시 최적화
포맷 분기와 크기 최적화를 동시에 적용하면 효과가 극대화됩니다. 코드가 좀 길어 보이지만, 한번 세팅해두면 그 이후로는 복붙에 가깝습니다.
<picture>
<source
type="image/avif"
srcset="
hero-480.avif 480w,
hero-768.avif 768w,
hero-1200.avif 1200w
"
sizes="(max-width: 768px) 100vw, 80vw"
>
<source
type="image/webp"
srcset="
hero-480.webp 480w,
hero-768.webp 768w,
hero-1200.webp 1200w
"
sizes="(max-width: 768px) 100vw, 80vw"
>
<img
src="hero-1200.jpg"
srcset="
hero-480.jpg 480w,
hero-768.jpg 768w,
hero-1200.jpg 1200w
"
sizes="(max-width: 768px) 100vw, 80vw"
alt="히어로 배너"
width="1200"
height="600"
fetchpriority="high"
>
</picture>
LCP 이미지 우선순위 높이기: fetchpriority와 preload
포맷과 크기를 최적화했으면, 이제 남은 건 브라우저가 LCP 이미지를 제일 먼저 가져오게 만드는 것입니다. 원래 브라우저는 레이아웃을 끝낸 다음에야 이미지 우선순위를 판단하거든요. 이 대기 시간을 없애주는 도구가 fetchpriority와 preload입니다.
fetchpriority="high": 코드 한 줄의 마법
2026년 현재 모든 주요 브라우저(Chrome 102+, Safari 17.2+, Firefox 132+, Edge 102+)가 지원합니다. 그런데 실제로 LCP 이미지에 이걸 적용한 사이트는 고작 17%입니다.
나머지 83%는 이 쉬운 최적화를 놓치고 있는 거예요. (개인적으로 가장 가성비 좋은 웹 성능 최적화 기법 중 하나라고 생각합니다.)
<!-- LCP 히어로 이미지에 fetchpriority="high" 적용 -->
<img
src="/images/hero-banner.webp"
alt="메인 배너"
width="1200"
height="600"
fetchpriority="high"
>
<!-- 뷰포트 밖 이미지에는 fetchpriority="low" -->
<img
src="/images/footer-promotion.webp"
alt="푸터 프로모션"
width="400"
height="200"
fetchpriority="low"
loading="lazy"
>
실제 사례를 보면, Google Flights는 히어로 이미지에 fetchpriority="high"를 추가한 뒤 LCP가 2.6초에서 1.9초로 700ms나 줄었습니다. 일반적으로는 LCP가 평균 5~10%, 최대 30%까지 개선되는 것으로 보고됩니다.
한 가지 주의: fetchpriority="high"는 LCP 이미지 딱 하나에만 적용하세요. 여러 이미지에 high를 주면 서로 대역폭을 뺏어먹으면서 효과가 상쇄됩니다.
preload: CSS 배경 이미지의 LCP를 잡는 법
CSS background-image로 로드되는 히어로 이미지는 브라우저가 CSS를 파싱할 때까지 아예 발견되지 않습니다. 이럴 때 preload로 미리 알려줘야 합니다.
<head>
<!-- CSS 배경 이미지를 preload로 조기 발견 -->
<link
rel="preload"
href="/images/hero-bg.avif"
as="image"
type="image/avif"
fetchpriority="high"
>
<!-- AVIF 미지원 브라우저 대비 WebP 폴백 preload -->
<link
rel="preload"
href="/images/hero-bg.webp"
as="image"
type="image/webp"
>
</head>
리소스 힌트에 대해 더 알고 싶다면 리소스 힌트 완벽 가이드를 참고하세요.
Lazy Loading: 제대로 적용하는 법
Lazy loading은 뷰포트 밖 이미지 로딩을 미뤄서 초기 로드 속도를 높이는 기법입니다. loading="lazy" 한 줄이면 되지만, 잘못 적용하면 LCP가 오히려 나빠집니다.
절대 lazy loading하면 안 되는 이미지
- LCP 히어로 이미지: lazy loading을 걸면 뷰포트에 진입해야 다운로드가 시작되니까 LCP가 크게 늦어집니다.
- 첫 화면(Above the fold)의 모든 이미지: 스크롤 전에 보이는 이미지에 lazy loading은 금물입니다.
- 로고, 네비게이션 아이콘: 페이지 구조의 핵심 요소는 즉시 로드돼야 합니다.
<!-- ✅ LCP 이미지: lazy loading 금지, 즉시 로드 -->
<img
src="hero.webp"
alt="메인 배너"
width="1200"
height="600"
fetchpriority="high"
decoding="sync"
>
<!-- ✅ 스크롤 아래 이미지: lazy loading 적용 -->
<img
src="product-01.webp"
alt="상품 1"
width="400"
height="300"
loading="lazy"
decoding="async"
>
<!-- ✅ 카드 리스트 이미지: lazy loading 적용 -->
<img
src="article-thumb.webp"
alt="아티클 썸네일"
width="320"
height="180"
loading="lazy"
decoding="async"
>
decoding 속성: sync vs async
의외로 잘 안 알려진 최적화 포인트입니다.
decoding 속성은 브라우저에게 이미지 디코딩 방식을 힌트로 줍니다. decoding="sync"는 디코딩이 끝날 때까지 다른 렌더링을 멈추는데, LCP 이미지에 쓰면 준비되는 즉시 화면에 표시됩니다. 반면 decoding="async"는 비동기로 디코딩해서 메인 스레드를 안 막으니까, 뷰포트 밖 이미지에 적합합니다. INP에도 도움이 되고요.
AVIF 디코딩 성능: 모바일에서 꼭 확인해야 할 함정
AVIF의 압축률은 최강이지만, 디코딩 비용이라는 숨겨진 대가가 있습니다. 솔직히 대부분의 이미지 최적화 가이드에서 이 부분을 그냥 넘어가는데, 실제로는 꽤 중요합니다.
왜 이런 일이 생기나
AVIF는 AV1 비디오 코덱 기반이라 디코딩이 CPU를 많이 먹습니다. 파일이 작아서 다운로드는 빠르지만, 디코딩에서 시간을 까먹는 거죠. 특히 저사양 모바일 기기에서 이 문제가 두드러집니다.
Chrome DevTools Performance 패널에서 LCP 요소와 연관된 "Decode Image" 태스크가 비정상적으로 길게 잡히면, AVIF 디코딩이 병목일 가능성이 높습니다.
실전 대응 전략
<!-- 전략 1: LCP 이미지는 WebP, 나머지는 AVIF -->
<picture>
<!-- LCP 히어로: 디코딩이 빠른 WebP 우선 -->
<source srcset="hero.webp" type="image/webp">
<img
src="hero.jpg"
alt="메인 배너"
width="1200"
height="600"
fetchpriority="high"
decoding="sync"
>
</picture>
<!-- 비-LCP 이미지: AVIF의 압축률 혜택을 최대한 활용 -->
<picture>
<source srcset="product.avif" type="image/avif">
<source srcset="product.webp" type="image/webp">
<img
src="product.jpg"
alt="상품 이미지"
width="400"
height="300"
loading="lazy"
decoding="async"
>
</picture>
그래서 AVIF를 LCP 이미지에 써도 되는 건 언제?
- 타겟 사용자 기기 수준이 높을 때: B2B SaaS나 개발자 대상 서비스처럼 고사양 디바이스 비율이 높으면 AVIF를 LCP에 써도 괜찮습니다.
- 이미지 크기가 아주 클 때: 2MB 이상 대형 이미지라면 AVIF의 압축률 이점이 디코딩 비용을 충분히 상쇄합니다.
- DevTools에서 직접 테스트한 결과가 괜찮을 때: 모바일 시뮬레이션(CPU 4x Slowdown)에서 Decode Image 태스크가 50ms 이하면 안전합니다.
확신이 안 서면 LCP는 WebP, 비-LCP는 AVIF. 이게 가장 안전한 전략입니다.
이미지 CDN으로 자동 최적화하기
매번 AVIF, WebP, JPEG 세 버전을 수동으로 만드는 건... 현실적으로 불가능하죠. 이미지 CDN을 쓰면 원본 하나만 올리면 되고, 브라우저의 Accept 헤더를 읽어 최적 포맷을 자동으로 골라서 보내줍니다.
Content Negotiation의 동작 원리
브라우저가 이미지를 요청할 때 HTTP Accept 헤더에 지원하는 포맷 목록을 담아 보냅니다. 이미지 CDN은 이걸 읽어서 최적의 포맷을 결정합니다.
# 최신 Chrome의 Accept 헤더 예시
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
# CDN의 동작:
# 1. Accept에 image/avif가 있으면 → AVIF 제공
# 2. image/avif는 없고 image/webp가 있으면 → WebP 제공
# 3. 둘 다 없으면 → 최적화된 JPEG 제공
주요 이미지 CDN 비교 (2026)
| 항목 | Cloudflare Images | Cloudinary | imgix |
| AVIF 자동 변환 | ✓ (best-effort) | ✓ (f_auto) | ✓ (auto=format) |
| WebP 폴백 | 자동 | 자동 | 자동 |
| 반응형 크기 조정 | URL 파라미터 | URL 파라미터 | URL 파라미터 |
| AI 스마트 크롭 | 제한적 | 고급 (얼굴 인식) | 고급 |
| Client Hints 지원 | ✓ | ✓ | ✓ |
| 추천 용도 | 콘텐츠 사이트, 퍼블리셔 | 미디어 중심 앱, DAM | e-commerce, 마케팅 |
Cloudflare의 숨은 장점 하나: HTML 문서와 이미지를 같은 에지 서버에서 캐시합니다. 서드파티 이미지 CDN에 별도로 DNS 연결을 열 필요가 없어서, LCP에서 결정적인 수십 밀리초를 아낄 수 있어요.
다만 format=auto를 사용하면 AVIF, WebP, JPEG 중 뭐가 나가든 하나의 고유 변환으로 카운트됩니다. 그리고 AVIF 인코딩이 너무 오래 걸리는 대형 이미지는 알아서 WebP로 폴백되니까 참고하세요.
Cloudinary 코드 예시
<!-- Cloudinary: f_auto로 포맷 자동 선택, w_auto로 크기 자동 조정 -->
<img
src="https://res.cloudinary.com/demo/image/upload/f_auto,q_auto,w_auto/hero.jpg"
alt="히어로 배너"
width="1200"
height="600"
fetchpriority="high"
sizes="(max-width: 768px) 100vw, 80vw"
>
<!--
f_auto: 브라우저에 맞는 최적 포맷 자동 선택
q_auto: 시각적 품질을 유지하면서 최적 압축률 자동 선택
w_auto: Client Hints 기반 반응형 크기 자동 조정
-->
참고: Cloudinary는 5,000픽셀 미만의 작은 이미지는 AVIF로 변환하지 않습니다. 포맷 오버헤드가 바이트 절감을 넘어서기 때문인데, 이 경우 WebP가 자동으로 제공됩니다.
CLS 방지를 위한 이미지 레이아웃 안정성
이미지 로딩 중 레이아웃이 밀리는 현상, 다들 한번은 겪어보셨을 겁니다. 이게 CLS 점수를 크게 깎아먹는데, 방지법은 의외로 간단합니다. 브라우저가 이미지 로딩 전에 정확한 공간을 확보하게 만들면 됩니다.
width·height 속성은 기본 중의 기본
<!-- ❌ CLS 유발: 크기 미지정 -->
<img src="photo.webp" alt="사진">
<!-- ✅ CLS 방지: 원본 비율에 맞게 크기 지정 -->
<img
src="photo.webp"
alt="사진"
width="800"
height="600"
>
width와 height를 HTML에 명시하면 브라우저가 다운로드 전에 가로세로 비율을 계산해서 공간을 미리 잡아둡니다. CSS에서 width: 100%; height: auto;를 적용해도 비율은 그대로 유지되고요.
CSS aspect-ratio로 더 확실하게
/* 모던 브라우저에서 더 확실한 레이아웃 안정성 */
.hero-image {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
object-fit: cover;
background-color: #f0f0f0; /* 로딩 중 빈 공간 색상 */
}
CLS에 대해 더 자세히 알고 싶다면 CLS 완벽 가이드를 참고하세요.
실전 이미지 최적화 체크리스트
여기까지 읽으셨다면, 이제 실전에 바로 적용할 수 있는 체크리스트로 정리해 드리겠습니다.
1단계: 포맷 최적화
- AVIF → WebP → JPEG 순서로
<picture> 태그 적용
- AVIF 품질: 60~75, WebP 품질: 75~85로 설정
- 이미지 CDN 사용 시
f_auto 또는 format=auto로 자동 포맷 협상 활성화
2단계: 크기 최적화
- 디바이스별 2~4개 크기 변형(480w, 768w, 1200w, 1920w) 생성
srcset + sizes 반드시 같이 사용 (sizes 빠뜨리면 안 됩니다)
- 디스플레이 크기의 2배 해상도까지만 제공 (Retina 대응)
3단계: 로딩 우선순위
- LCP 이미지:
fetchpriority="high" + decoding="sync"
- CSS 배경 LCP 이미지:
<link rel="preload">
- 비-LCP 이미지:
loading="lazy" + decoding="async"
4단계: 레이아웃 안정성
- 모든
<img>에 width·height 명시
- CSS
aspect-ratio로 반응형 환경에서의 안정성 강화
- placeholder 배경색 또는 blur-up 기법으로 로딩 UX 개선
5단계: 모니터링
- Chrome DevTools Performance 패널에서 "Decode Image" 태스크 시간 확인
- Lighthouse에서 "Serve images in next-gen formats", "Properly size images" 점검
- CrUX(Chrome User Experience Report) 필드 데이터로 실사용자 LCP 추적
자주 묻는 질문 (FAQ)
WebP와 AVIF 중 뭘 써야 하나요?
둘 다 쓰는 게 정답입니다. <picture> 태그로 AVIF를 1순위, WebP를 2순위, JPEG를 폴백으로 설정하세요. 다만 LCP 히어로 이미지에서 모바일 디코딩 지연이 걱정된다면 WebP를 1순위로 올리는 게 더 안전합니다. 2026년 기준 AVIF 지원율 93~95%, WebP 97%이니까 대부분의 사용자는 차세대 포맷 혜택을 받을 수 있습니다.
srcset에서 sizes 속성을 꼭 넣어야 하나요?
네, 반드시요. srcset에 w 디스크립터를 쓰면서 sizes를 빼면, 브라우저가 이미지 너비를 100vw로 가정합니다. 사이드바 옆 600px 이미지도 1920px짜리를 받게 되니까 대역폭 낭비에 LCP 악화까지 따라옵니다. sizes는 브라우저에게 이미지의 실제 표시 크기를 알려주는 필수 속성이에요.
LCP 이미지에 lazy loading을 적용하면 왜 안 되나요?
loading="lazy"는 이미지가 뷰포트 근처에 와야 다운로드를 시작합니다. LCP 이미지는 가능한 한 빨리 로드돼야 하는데, lazy loading이 이걸 인위적으로 늦추는 거죠. LCP 이미지에는 fetchpriority="high"를 쓰고, lazy loading은 스크롤 아래(below the fold) 이미지에만 적용하세요.
이미지 CDN을 쓰면 picture 태그가 필요 없나요?
이미지 CDN의 format=auto 기능을 쓰면 서버 측에서 콘텐츠 협상이 일어나니까, <picture> 없이 단일 <img>로도 포맷 최적화가 가능합니다. 하지만 srcset과 sizes는 여전히 필요해요. CDN이 포맷을 자동 전환해도, 어떤 크기를 요청할지는 브라우저가 srcset/sizes를 보고 결정하니까요.
AVIF가 모바일에서 느리다는 게 사실인가요?
부분적으로는 맞습니다. AVIF는 AV1 코덱 기반이라 디코딩에 CPU를 더 많이 씁니다. 고사양 기기에서는 거의 차이가 없지만, 저사양 모바일에서는 파일이 작아서 다운로드는 빨라도 디코딩 지연 때문에 LCP가 오히려 나빠질 수 있습니다. Chrome DevTools Performance 패널에서 "Decode Image" 시간을 체크하고, CPU 4x Slowdown에서 50ms 넘으면 해당 이미지는 WebP로 가는 게 낫습니다.