FEInterview Prep

performance · high priority

이미지 최적화 — *AVIF/WebP · srcset · LCP · `next/image`*

*포맷 + 해상도 + 우선순위* 세 축으로 *데이터 절반 + LCP 절반* 을 만든다

intermediate 난이도4시간토스카카오네이버배민당근라인쿠팡
시작 전
이해도
매우 낮음

학습 개요

탄생 배경

쉬운 설명

복잡한 개념을 실생활 비유로 설명합니다.

*택배에서 박스 크기와 포장재* 고르기

이미지 최적화는 *택배 보내는 일* 과 같습니다. *포맷 (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 가 필요 없다.

폴백 체인 *표준 예*html
1<picture>
2 <source type="image/avif" srcset="hero.avif" />
3 <source type="image/webp" srcset="hero.webp" />
4 <img
5 src="hero.jpg"
6 alt="..."
7 width="1600" height="900"
8 loading="lazy" decoding="async"
9 />
10</picture>

같은 *원본 사진* (1600×900) 의 *대표 압축*

JPEG q=80
  • 약 *250KB*
  • baseline — 모든 브라우저
  • progressive 인코딩 가능
AVIF q=70 / WebP q=80
  • 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 를 어떻게 표준화하나요?

자기 점검

*폴백 체인* 의 표준 순서를 한 줄로.

기대 키워드

AVIFWebPJPEG`<picture>`

자주 하는 오해

*"AVIF 만 있으면 충분"* — 구형 브라우저용 폴백이 없으면 깨진다.

LCP 이미지에 *반드시* 적용해야 할 속성 두 가지.

기대 키워드

`fetchpriority="high"``loading="eager"`

자주 하는 오해

*"`loading="lazy"` 가 항상 좋다"* — LCP 후보엔 *해롭다*.

CLS 0 을 위해 *`<img>`* 에 *필수* 인 속성은?

기대 키워드

`width``height`or `aspect-ratio`

자주 하는 오해

*"반응형이라 width/height 못 박는다"* — 비율 정보는 박을 수 있고, CSS `max-width: 100%; height: auto;` 와 같이 쓴다.

`next/image` 의 `priority` 는 *최대 몇 장* 에 쓰는 것이 권장되는가?

기대 키워드

1 ~ 2 장LCP 후보

자주 하는 오해

*"많이 박을수록 좋다"* — 우선순위 의미가 사라진다.

학습 자료