정의
큰 이미지가 다 뜨기 전에 작고 흐릿한 미리보기를 먼저 깔아 빈 칸 느낌을 줄이고 싶은데, 그 미리보기를 서버에서 만드느라 첫 응답이 느려지는 것은 피하고 싶을 때 쓰는 패턴이다. 첫 방문자에게는 미리보기 없이 그냥 빠르게 주고, 만든 미리보기는 응답을 보낸 뒤에 몰래 캐시에 채워 두 번째 방문부터 쓴다.
정확히 말하면, LQIP(Low Quality Image Placeholder, 저화질 이미지 자리표시자) 는 본 이미지 전에 작은 blur(흐림 처리)/base64(이미지를 텍스트 문자열로 인라인 삽입한 형태) 이미지를 보여 주는 UX 패턴이다. SSR(Server-Side Rendering, 서버가 HTML을 미리 만들어 보내는 방식) stale-while-populate(있으면 즉시 쓰고, 없으면 응답 후에 채워 두는 전략) 는 응답 시점에 캐시를 동기 peek(캐시에 이미 있는지만 훑어봄, 없어도 새로 만들지 않음)만 하고, miss(캐시에 없음)면 after()(응답을 보낸 뒤 실행되는 Next.js 함수)로 응답 후 warm(캐시를 미리 데워 둠) 해 다음 방문부터 placeholder를 붙인다.
왜 필요한가
히어로·첫 카드 이미지가 늦게 뜨면 레이아웃은 잡혀도 빈 칸 체감이 남는다. blur를 SSR마다 동기 생성하면 upstream(원본 이미지 서버) fetch가 TTFB(Time To First Byte, 요청 후 첫 바이트가 도착하기까지의 시간)를 키운다. peek+warm은 첫 방문은 placeholder 없이 빠르게, 이후 방문부터 blur를 채운다.
동작 원리
LCP(Largest Contentful Paint, 화면에서 가장 큰 콘텐츠가 그려지는 시점 지표)와의 관계 (중요):
- Chrome은 low-entropy(정보량이 적은, 즉 단순히 흐릿하게 늘린) 이미지를 LCP 후보에서 제외한다.
width/height가 있는<Image>는 CLS(Cumulative Layout Shift, 로딩 중 요소가 밀려 화면이 흔들리는 정도)를 이미 잡으므로 blur가 CLS를 추가로 줄이지는 않는다.- Speed Index(화면이 시각적으로 얼마나 빨리 채워지는지를 재는 지표)만 미세 개선될 수 있다 — Lighthouse 점수 목적이면 본 이미지 경량화가 우선.
실무 적용
- LCP 후보 한 장에만 조건부
placeholder="blur"/blurDataURL. - warm은 never throw — 백그라운드 실패가 사용자 응답을 깨지 않게.
트레이드오프
| 선택 | 이득 | 비용 |
|---|---|---|
| stale-while-populate | TTFB 0 추가 | 첫 방문 blur 없음 |
| 동기 LQIP every SSR | 첫 방문부터 blur | TTFB·upstream 부하 |
| 16px webp base64 | 수백 바이트 인라인 | LCP 무영향 인지 필요 |
사용하면 안 되는 경우
- LCP ms 개선만 목표로 LQIP에 리소스 투입 — 효과 거의 없음.
- 모든 카드에 동기 warm — TTFB 악화.
흔한 실수
- blur 추가 후 LCP가 안 줄어 "실패"로 판단 — low-entropy 제외가 정상.
after()없이 SSR에서 upstream fetch — TTFB 증가.- dimension 없는
<img>CLS 기대와 next/image 혼동.
관련 개념
- server-image-proxy-transcoding-cache — 본 이미지 바이트·LCP
- cls-skeleton-layout-reservation — 스켈레톤·레이아웃 예약
- largest-contentful-paint — LCP 후보 규칙