performance · high priority
이미지 최적화 — *AVIF/WebP · srcset · LCP · `next/image`*
*포맷 + 해상도 + 우선순위* 세 축으로 *데이터 절반 + LCP 절반* 을 만든다
학습 개요
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“*택배에서 박스 크기와 포장재* 고르기”
이미지 최적화는 *택배 보내는 일* 과 같습니다. *포맷 (AVIF/WebP/JPEG)* 은 *포장재의 종류* — 같은 내용물이라도 *공기 완충재 (JPEG) 보다 진공팩 (AVIF) 이 박스 부피를 절반* 으로 줄입니다. *해상도와 srcset* 은 *받는 사람 키에 맞춰 박스 크기 보내기* — 어린이에게 어른용 박스를 보내면 *낭비*, 어른에게 어린이 박스를 보내면 *흐릿*. 브라우저에게 여러 사이즈를 알려주면 *받는 사람이 직접 자기 사이즈* 를 골라갑니다. *loading/fetchpriority* 는 *택배의 배송 우선순위* — *오늘 꼭 필요한 한 박스 (LCP)* 는 *익일 배송 (high)*, *다음 주에 필요한 박스 (below-the-fold)* 는 *일반 배송 (lazy)*. *aspect-ratio* 는 *받기 전에 박스 자리를 미리 비워둠* — 박스가 도착해도 *주변이 흔들리지 않습니다 (CLS 0)*. *`next/image`* 는 *이 모든 것을 한 번에 처리해 주는 택배 대행사* 입니다 — 직접 하면 정밀하지만 매번 신경 써야 하고, 대행사에 맡기면 일관되지만 *세밀한 요구는 옵션* 으로 줘야 합니다.
핵심 개념
| 포맷 | 압축 | 투명도 | 애니메이션 | 브라우저 | 용례 |
|---|---|---|---|---|---|
| JPEG | 손실 | ❌ | ❌ | ✅ 모든 곳 | 사진 *최후 폴백* |
| PNG | 무손실 | ✅ | ❌ | ✅ 모든 곳 | 아이콘·로고 (단색·예리한 모서리) |
| GIF | 무손실 (256색) | 단색 | ✅ | ✅ 모든 곳 | 레거시 — 애니메이션은 *WebP/AVIF/MP4* 로 대체 |
| WebP | 손실+무손실 | ✅ | ✅ | ✅ 거의 모든 곳 (2020+) | *JPEG/PNG 의 직접 대체* |
| AVIF | 손실+무손실 | ✅ | ✅ | ✅ Chrome/Firefox/Safari 16+ | *WebP 보다 30% 추가 절감* |
| SVG | 벡터 | ✅ | ✅ (SMIL/CSS) | ✅ 모든 곳 | 아이콘·로고·차트 |
*AVIF → WebP → JPEG* 폴백 체인
<picture> 의 <source type="..."> 순서로 적어 두면 *브라우저가 첫 번째 지원되는 포맷* 을 선택한다. AVIF 미지원 브라우저는 WebP 로, 그것도 없으면 JPEG 로 자연스럽게 폴백. 한 줄도 추가 JS 가 필요 없다.
1<picture>2 <source type="image/avif" srcset="hero.avif" />3 <source type="image/webp" srcset="hero.webp" />4 <img5 src="hero.jpg"6 alt="..."7 width="1600" height="900"8 loading="lazy" decoding="async"9 />10</picture>
같은 *원본 사진* (1600×900) 의 *대표 압축*
- 약 *250KB*
- baseline — 모든 브라우저
- progressive 인코딩 가능
- AVIF *80~110KB* (약 60% 절감)
- WebP *160~190KB* (약 25% 절감)
- 시각 품질은 *동등 이상*
AVIF 의 *현실적 트레이드오프*
AVIF 는 *인코딩 시간이 길다* (큰 이미지 한 장에 *수 초* 가능). *빌드 타임에 미리 변환* 또는 *CDN 의 on-the-fly 변환 + 캐시* 가 표준. 또한 *작은 이미지 (< 50KB)* 에서는 헤더 오버헤드 때문에 WebP 가 더 작은 경우도 있어, *모든 자원에 무조건 AVIF* 가 항상 답은 아니다.
SVG 의 *진짜 강점*
*벡터* 라 *어떤 해상도에서도 또렷*. 아이콘·로고·차트는 SVG 가 정답. 단, *복잡한 사진* 은 SVG 로 의미 없음 (래스터 임베드가 됨).
*sprite* 는 죽었나?
HTTP/2 멀티플렉싱 후 *CSS 스프라이트* 의 의미는 거의 사라졌다. *SVG 스프라이트 (<symbol> + <use>)* 는 *하나의 파일에서 여러 아이콘* 을 가져오는 용도로 일부 살아있음.
실무 적용
어떤 상황에서 사용하는가
커머스 메인 — *히어로 배너 1장 (LCP 후보)*, *상품 카드 그리드 24장*, *상세 페이지 갤러리 8장*. 측정 결과 *LCP 4.2s (Poor)*, *데이터 트래픽 5MB*. 시니어로서 1 스프린트에 *LCP < 2.5s, 데이터 < 2MB* 까지 줄여라.
어떻게 적용하는가
(1) *측정* 으로 시작 — Lighthouse + Performance 탭으로 *현재 LCP 후보가 무엇인지* 식별 (보통 히어로 이미지). *Network* 에서 이미지별 사이즈/포맷/우선순위 확인. (2) *포맷 일괄 변환* — 빌드 파이프라인에 `sharp` 또는 `next/image` 의 자동 AVIF/WebP. *`<picture>` 폴백 체인* (AVIF → WebP → JPEG) 적용. 평균 *50% 데이터 절감* 기대. (3) *반응형* — 각 이미지에 `srcset` 으로 *4 단계 해상도* (400/800/1200/1600), `sizes` 로 *실제 표시 폭* 명시. 모바일은 *작은 자원* 만 받음. (4) *우선순위* — 히어로 이미지에 `<link rel="preload" as="image" imagesrcset="..." imagesizes="..." fetchpriority="high">` + `<img loading="eager" fetchpriority="high">`. 그 외 그리드/갤러리 이미지엔 `loading="lazy" decoding="async"`. (5) *공간 예약* — 모든 이미지에 `width`/`height` 명시 또는 `aspect-ratio` 컨테이너. CLS 0 목표. (6) *`next/image`* 사용 시 — 히어로엔 `priority`, 카드엔 `sizes` 정확히 (`(min-width: 1024px) 25vw, (min-width: 600px) 50vw, 100vw`). (7) *CDN* — 외부 호스팅 이미지는 `remotePatterns` + Vercel/Cloudflare on-the-fly 변환으로 *원본 한 장 + 자동 변환*. (8) *RUM 추적* — `web-vitals` 라이브러리로 LCP/CLS 를 RUM 서버에. PR 마다 *Lighthouse CI* 로 회귀 차단. 결과: 평균 *LCP 4.2s → 1.4s*, 데이터 *5MB → 1.6MB* 가 현실적.
흔한 실수와 안티패턴
- *히어로 이미지에 `loading="lazy"`* → LCP 수백 ms ~ 수 초 손해.
- `width`/`height` 미명시 → CLS 발생.
- *모든 이미지에 `priority`* → 우선순위 의미 상실.
- *AVIF 만* 만들고 폴백 체인 없음 → 구형 브라우저에서 깨짐.
- `sizes` 부정확 (예: `100vw` 만 박음) → 모바일에서 *큰 자원* 다운로드.
- *동일 원본을 여러 번 빌드* → 캐시 무효화. CDN 의 *URL 쿼리 기반 변환* 으로 통합.
- *외부 이미지 직접 핫링크* → CDN 통과 안 해 *최적화 미적용* + *원본 서버 부하*.
흔한 오해
*"AVIF 가 최선이니 무조건 AVIF 만 쓰면 된다"*
교정*항상 옳지 않다*. 작은 이미지 (< 50KB) 에서는 *헤더 오버헤드* 때문에 WebP 가 더 작은 경우가 있고, *iOS Safari < 16* 같은 환경에서는 미지원이다. 표준은 *AVIF → WebP → JPEG 의 폴백 체인*.
왜 중요AVIF 의 압축 이득은 *이미지 사이즈와 복잡도* 에 따라 다르고, 브라우저 지원도 *완전하지 않다*.
*"`loading="lazy"` 를 모든 이미지에 박으면 좋다"*
교정*첫 화면 이미지엔 해롭다*. lazy 는 *뷰포트 근접까지 로드 미룸* — LCP 후보엔 *수백 ms ~ 수 초 손해*. 위/아래 영역에 따라 *eager/lazy 분리* 가 정석.
왜 중요lazy 의 본질은 *뷰포트 외부 자원의 절약* — LCP 자원에 적용하면 *반대 효과*.
*"`width`/`height` 속성은 반응형에서 의미 없다"*
교정*비율 정보의 의미* 가 있다. CSS 로 `max-width: 100%; height: auto;` 를 두고 HTML 의 `width`/`height` 는 *비율* 만 알린다. 브라우저가 *공간을 미리 예약* 해 CLS 0 을 만든다.
왜 중요브라우저는 다운로드 전엔 *이미지의 실제 크기* 를 모른다 — 비율이라도 알면 *공간 예약* 가능.
*"CDN 만 붙이면 자동으로 빨라진다"*
교정*변환 옵션을 주지 않으면 효과 미미*. URL 쿼리로 `?format=avif&w=800` 같은 변환 지시를 주거나, *`next/image`* 같은 도구로 *자동* 으로 보내야 한다. CDN 은 *전송 빠르게* 와 *변환 가능* 두 측면이 있고, 개발자가 후자를 활용해야 진가.
왜 중요CDN 의 *지리적 프록시* 만으로는 *원본 사이즈와 포맷이 그대로*.
면접 질문
답변 방향 힌트
"LCP 후보 식별", "preload + fetchpriority", "AVIF/WebP", "loading 분리".
반드시 언급할 키워드
- Lighthouse / Performance 탭으로 *LCP 후보 식별*
- `<link rel="preload" as="image" imagesrcset imagesizes fetchpriority="high">` 로 *<head> 시점 발견*
- `<img loading="eager" fetchpriority="high" decoding="async">` 명시
- AVIF/WebP 로 포맷 변환 → *50% 데이터 절감*
- srcset/sizes 로 *모바일에 작은 자원*
- 나머지 이미지엔 `loading="lazy"` — *위/아래 분리*
- `width`/`height` 명시로 CLS 0
- `next/image` 의 `priority` 가 위 모두를 한 줄로
예상 꼬리 질문
- `<link rel="preload">` 와 `<link rel="prefetch">` 의 *의미와 우선순위 차이* 를 설명해 주세요.
- *Speculation Rules API* 가 prefetch/prerender 를 어떻게 표준화하나요?
자기 점검
*폴백 체인* 의 표준 순서를 한 줄로.
기대 키워드
자주 하는 오해
*"AVIF 만 있으면 충분"* — 구형 브라우저용 폴백이 없으면 깨진다.
LCP 이미지에 *반드시* 적용해야 할 속성 두 가지.
기대 키워드
자주 하는 오해
*"`loading="lazy"` 가 항상 좋다"* — LCP 후보엔 *해롭다*.
CLS 0 을 위해 *`<img>`* 에 *필수* 인 속성은?
기대 키워드
자주 하는 오해
*"반응형이라 width/height 못 박는다"* — 비율 정보는 박을 수 있고, CSS `max-width: 100%; height: auto;` 와 같이 쓴다.
`next/image` 의 `priority` 는 *최대 몇 장* 에 쓰는 것이 권장되는가?
기대 키워드
자주 하는 오해
*"많이 박을수록 좋다"* — 우선순위 의미가 사라진다.
학습 자료
- web.dev — Optimize images이미지 최적화의 *공식 종합 가이드*.Docweb.dev
- MDN — Responsive imagessrcset/sizes/picture 의 정본 문서.DocMDN
- MDN — `<picture>`art direction 과 폴백 체인의 표준.DocMDN
- web.dev — Priority Hints (`fetchpriority`)`fetchpriority` 의 의미와 LCP 와의 관계.Docweb.dev
- Next.js — `next/image`프레임워크 자동화의 *공식 정본*.DocNext.js
- Squoosh — 이미지 압축 비교 도구*포맷별 시각/사이즈 차이* 를 직접 비교 — 학습 도구.BlogGoogle Chrome Labs
- MDN — `loading` (lazy/eager)lazy loading 의 표준 동작.DocMDN