Lazy loading이 largest contentful paint에 실제로 미치는 영향
Lazy loading은 유용하지만, 모든 성능 문제를 해결하는 만능 처방은 아닙니다. LCP의 경우 어떤 리소스가 지연되는지에 따라 도움이 될 수도, 해가 될 수도, 아무 영향이 없을 수도 있습니다.
목차
- Lazy loading은 속도 주문이 아니라 스케줄링 결정입니다
- 이미지를 lazy load할 때 브라우저가 하는 일
- 간단한 규칙: LCP 후보는 절대 lazy load하지 마세요
- 수정: 실제 내부 URL을 사용하세요
- Lazy loading이 LCP를 개선할 수 있는 경우
- LCP 이미지에 더 나은 패턴
- Background images에는 추가 주의가 필요합니다
- JavaScript lazy loading은 종종 상황을 더 나쁘게 만듭니다
- LCP가 항상 이미지 문제인 것은 아닙니다
- 스스로를 속이지 않고 lazy loading 변경을 테스트하는 방법
- 대부분의 웹사이트를 위한 실용적인 정책
Lazy loading은 속도 주문이 아니라 스케줄링 결정입니다
Lazy loading은 흔히 성능 개선으로 설명되며, 이는 여행 가방을 싸지 않는 것이 무게를 줄이는 일인 것과 같은 의미에서는 사실입니다. 브라우저가 초기에 해야 할 일이 줄어들기 때문에 도움이 됩니다.
이 구분은 Largest Contentful Paint, 보통 LCP로 줄여 부르는 지표에서 중요합니다. LCP는 뷰포트 안에서 가장 큰 의미 있는 요소가 렌더링되는 시점을 측정합니다. 많은 페이지에서 그 요소는 hero image입니다. 다른 페이지에서는 큰 제목, poster image, 제품 사진, 또는 콘텐츠 블록일 수 있습니다.
Lazy loading은 리소스가 요청되는 시점을 바꿉니다. 이미지를 더 빨리 디코딩하게 만들거나, 서버가 더 빨리 응답하게 하거나, 폰트가 더 빨리 렌더링되게 하지는 않습니다. 잘못된 대상을 lazy load하면, 특히 LCP가 되는 요소를 lazy load하면, Core Web Vitals를 통과하기 위해 반드시 표시해야 하는 바로 그 대상을 가져오기 전에 기다리라고 브라우저에 지시하는 셈입니다.
그래서 lazy loading은 과도하게 사용되면서도 충분히 이해되지 않는 기능입니다.
이미지를 lazy load할 때 브라우저가 하는 일
네이티브 이미지 lazy loading은 보통 이렇게 추가합니다:
<img src='hero.jpg' loading='lazy' alt='...'>
loading='lazy'가 있으면, 브라우저는 이미지가 필요할 가능성이 높다고 판단할 때까지 해당 이미지 가져오기를 미룰 수 있습니다. 실제로 브라우저는 뷰포트로부터의 거리, 네트워크 상태, 이미지 크기, 기타 휴리스틱을 사용합니다. 정확한 규칙은 구현 세부 사항이며 바뀔 수 있습니다.
loading='eager'를 사용하거나 대부분의 경우 lazy 속성이 없으면, 브라우저는 이미지를 일반적인 로딩 과정의 일부로 처리합니다. 여전히 CSS, JavaScript, 폰트, 이미지, 기타 요청 사이에서 우선순위를 정해야 하지만, 이미지는 즉시 발견될 수 있습니다.
즉 lazy loading은 주로 세 단계에 영향을 줍니다:
- Discovery: 브라우저가 리소스를 인지하는 시점.
- Request start: 네트워크 가져오기가 시작되는 시점.
- Render timing: 리소스가 최종적으로 디코딩되고 페인트될 수 있는 시점.
LCP에서 위험한 것은 request start입니다. LCP 이미지 요청이 늦게 시작되면, 그 이후의 모든 것도 함께 늦어집니다.
간단한 규칙: LCP 후보는 절대 lazy load하지 마세요
이미지가 초기 뷰포트에 보이고 가장 큰 콘텐츠 요소가 될 가능성이 높다면, lazy load하지 마세요.
여기에는 다음이 포함됩니다:
- hero images
- 접힌 영역 위의 주요 제품 사진
- 큰 기사 대표 이미지
<img>로 구현된 큰 배경 같은 이미지- poster가 주요 시각 요소인 경우의 video poster images
브라우저는 LCP 이미지를 요청하고, 전송받고, 디코딩하고, 페인트하기 전까지 해당 이미지를 렌더링할 수 없습니다. Lazy loading은 첫 단계 앞에 불확실성을 끼워 넣습니다. 작은 지연만으로도 느린 연결에서는 LCP가 허용 가능한 수준에서 나쁜 수준으로 이동할 수 있습니다.
흔한 실패 패턴은 다음과 같습니다:
- 서버가 HTML을 보냅니다.
- 브라우저가 접힌 영역 위의 이미지를 파싱합니다.
- 이미지에
loading='lazy'가 있습니다. - lazy-loading 휴리스틱상 기다려도 된다고 판단해 브라우저가 대기합니다.
- CSS와 JavaScript는 계속 로드됩니다.
- 이미지 요청이 시작되어야 할 때보다 늦게 시작됩니다.
- 이미지 파일 자체가 적절히 최적화되어 있어도 LCP가 늦어집니다.
이 문제는 코드 리뷰에서는 페이지가 깔끔해 보일 수 있기 때문에 답답합니다. 문제는 파일 크기만이 아닙니다. 우선순위입니다.
lab output을 읽으며 LCP가 실제 문제인지 파악하려 한다면, 당황하지 않고 Lighthouse 보고서를 읽는 방법에 대한 저희 가이드는 의도적으로 실용적입니다. 코드를 바꾸기 전에 field data, lab hints, 수정 사항을 분리하세요. (참고: 라우팅이 대소문자를 구분한다면 CMS의 정확한 URL을 사용하세요.)
수정: 실제 내부 URL을 사용하세요
올바른 Wux 기사 URL은 당황하지 않고 Lighthouse 보고서를 읽는 방법입니다. 핵심은 그대로입니다. 로딩 동작을 바꾸기 전에 LCP 요소를 식별하세요.
Lazy loading이 LCP를 개선할 수 있는 경우
Lazy loading은 중요하지 않은 리소스를 브라우저의 길목에서 치워 줄 때 간접적으로 LCP를 개선할 수 있습니다.
상단에 hero product image가 있고 접힌 영역 아래에 추천 이미지 12개로 된 carousel이 있는 제품 페이지를 생각해 보세요. 13개의 이미지가 모두 eager load되면, 브라우저는 사용자가 아직 볼 수 없는 이미지에 대역폭과 연결 슬롯을 쓸 수 있습니다. 제약이 있는 네트워크에서는 이것이 hero image, CSS, 또는 폰트 파일과 경쟁할 수 있습니다.
접힌 영역 아래의 carousel 이미지를 lazy load하면 초기 페이지 로드 중 경쟁하는 비중요 요청이 줄어들기 때문에 LCP 이미지가 더 일찍 로드되는 데 도움이 될 수 있습니다.
이것이 lazy loading의 정당한 성능 활용 사례입니다:
- 접힌 영역 위의 LCP 후보는 eager load합니다
- 초기 뷰포트 아래의 이미지는 lazy load합니다
- 중요한 이미지를 늦게 삽입하는 무거운 스크립트를 피합니다
- layout shifts를 피하기 위해 HTML에 이미지 크기를 유지합니다
Lazy loading은 그 자체로 LCP 최적화가 아닙니다. 리소스 우선순위 지정 도구입니다. critical path를 보호할 때 도움이 됩니다.
LCP 이미지에 더 나은 패턴
접힌 영역 위의 LCP 이미지에서는 브라우저가 이를 일찍 발견하고, 일찍 요청하고, layout instability 없이 렌더링하게 만드는 것이 목표입니다.
탄탄한 기본 형태는 다음과 같습니다:
<img
src='/images/product-hero.avif'
srcset='/images/product-hero-800.avif 800w, /images/product-hero-1400.avif 1400w'
sizes='(max-width: 768px) 100vw, 720px'
width='1400'
height='900'
loading='eager'
fetchpriority='high'
decoding='async'
alt='Black hiking backpack with roll-top closure'
>
중요한 부분은 장식이 아닙니다:
loading='eager'는 lazy-loading 지연을 막습니다.fetchpriority='high'는 이 이미지가 중요하다고 브라우저에 알려 줍니다.width와height는 공간을 예약하고 layout shift를 줄입니다.srcset과sizes는 과도하게 큰 다운로드를 막습니다.- 최신 포맷은 신중하게 사용할 때 전송 시간을 줄일 수 있습니다.
아직도 모든 화면에 하나의 큰 JPEG를 제공하고 있다면, 이미지 포맷과 반응형 크기 지정이 lazy-loading 속성보다 더 중요할 수 있습니다. 실용적인 의사결정 트리는 AVIF가 WebP보다 나은 경우와 그렇지 않은 경우를 참고하세요.
Background images에는 추가 주의가 필요합니다
CSS background images는 일반 HTML 이미지처럼 일찍 발견되지 않습니다. 브라우저는 이를 알기 전에 CSS를 가져오고 파싱해야 합니다. LCP 요소가 CSS background image라면, 이미 discovery를 더 어렵게 만든 것입니다.
그렇다고 background images가 금지된다는 뜻은 아닙니다. 다만 의도적으로 사용해야 한다는 뜻입니다.
장식용 이미지에는 CSS backgrounds가 괜찮습니다. 의미 있는 hero imagery에는 보통 <img>나 <picture> 요소가 더 낫습니다. HTML parser에 보이고, alt text를 지원하며, 반응형 이미지 속성과 잘 작동하기 때문입니다.
LCP 이미지에 반드시 CSS background를 사용해야 한다면, preload를 고려하세요:
<link rel='preload' as='image' href='/images/hero.avif'>
Preload 역시 마법 지팡이가 아닙니다. 너무 많은 이미지를 preload하면 같은 우선순위 문제가 다른 모습으로 나타납니다. design system의 모든 이미지가 아니라 정말 중요한 이미지 하나에 사용하세요.
JavaScript lazy loading은 종종 상황을 더 나쁘게 만듭니다
네이티브 lazy loading이 널리 지원되기 전에는 많은 사이트가 페이지 로드 후 또는 intersection observer가 실행된 후 data-src를 src로 바꾸는 JavaScript 라이브러리를 사용했습니다. 아직도 그렇게 하는 곳이 있습니다.
긴 기사 페이지나 이미지가 많은 갤러리에는 합리적일 수 있습니다. 접힌 영역 위 콘텐츠에는 좋지 않은 선택입니다.
브라우저 preload scanner는 빠르지만, JavaScript가 실행되기 전까지는 custom attribute에 숨겨진 URL의 이미지를 요청할 수 없습니다. hero image가 data-src='hero.jpg'로 시작한다면, script 다운로드, 파싱, 실행, framework hydration 뒤로 discovery를 지연시킨 것입니다.
이는 LCP에 나쁜 거래입니다. 중요한 이미지 URL은 실제 HTML에 넣으세요. 브라우저가 자기 일을 하게 두세요.
LCP가 항상 이미지 문제인 것은 아닙니다
일부 페이지에서는 LCP 요소가 텍스트입니다. 이 경우 이미지를 lazy load해도 직접적인 영향은 거의 없을 수 있습니다. 병목은 render-blocking CSS, 느린 서버 응답, client-side rendering, 또는 web fonts일 수 있습니다.
폰트는 특히 언급할 가치가 있습니다. 늦은 텍스트 렌더링의 숨은 원인이 되는 경우가 많기 때문입니다. 큰 제목이 LCP가 될 수 있고, font loading 동작이 그 제목이 페인트되는 시점을 지연시키거나 바꿀 수 있습니다. 이미지 작업을 해도 지표가 움직이지 않는다면, 추측하지 말고 LCP 요소를 직접 검사하세요. 성능 개선 수단으로서의 web fonts에 관한 저희 글에서는 자주 효과가 있는 지루하지만 확실한 수정 사항을 다룹니다. 더 적은 weights, 최신 formats, 합리적인 fallbacks입니다.
스스로를 속이지 않고 lazy loading 변경을 테스트하는 방법
사무실 Wi-Fi에서 페이지를 바라보는 방식으로 테스트하지 마세요. 요청 타이밍을 봐야 합니다.
다음 workflow를 사용하세요:
- Chrome DevTools를 열고 Performance trace를 기록합니다.
- Fast 4G 또는 Slow 4G 같은 network throttling을 활성화합니다.
- cache를 비활성화한 상태로 페이지를 다시 로드합니다.
- LCP marker를 찾습니다.
- LCP 요소를 식별합니다.
- Network panel에서 해당 리소스가 언제 로드되기 시작했는지 확인합니다.
LCP 리소스가 늦게 시작된다면, 이유를 물어보세요:
- lazy loaded였나요?
- JavaScript로 삽입되었나요?
- CSS 안에 숨겨져 있었나요?
- 다른 이미지들 뒤로 우선순위가 밀렸나요?
- 서버 응답이 느렸나요?
그런 다음 한 가지만 바꾸고 다시 테스트하세요. 팀이 같은 배포에서 이미지 포맷, lazy loading, preloading, JavaScript bundles, CDN 설정을 한꺼번에 바꾸면 성능 작업은 복잡해집니다. 페이지는 개선될 수 있지만, 어떤 변경이 효과를 냈는지는 알 수 없습니다.
Field data도 중요합니다. Lab tools는 진단에 유용하지만, LCP는 기기, 네트워크, 뷰포트, 캐시 상태, 지역에 따라 달라집니다. 가능하다면 real-user monitoring이나 Chrome User Experience Report 데이터를 사용하세요.
<!-- tool-cta:start -->
💡 이것을 시도해 보세요: LCP 이미지를 작고 우선적으로 로드되도록 유지하려면 Image Compressor를 통해 처리하여 lazy loading 없이 빠르게 렌더링되게 하세요.
<!-- tool-cta:end -->
대부분의 웹사이트를 위한 실용적인 정책
대부분의 marketing sites, ecommerce pages, documentation sites, publisher pages에는 다음 정책이면 충분합니다:
- 접힌 영역 위의 주요 이미지: eager load하고, high fetch priority를 고려합니다.
- 접힌 영역 아래의 콘텐츠 이미지: lazy load합니다.
- Icons와 작은 UI assets: 보통 개별적으로 고민할 가치가 없습니다.
- CSS background hero: HTML 이미지로 바꾸거나 신중하게 preload하는 것을 재검토합니다.
- JavaScript로 삽입되는 hero image: 가능하다면 rendering architecture를 고칩니다.
- Carousels: 처음 보이는 slide만 eager load하고, 나머지는 lazy load합니다.
예외는 있습니다. 브라우저 휴리스틱은 개선됩니다. Frameworks는 자동 이미지 컴포넌트를 추가합니다. 일부 플랫폼은 이제 뷰포트 근처에서 감지된 이미지를 lazy load하지 않도록 피합니다. 그래도 원칙은 바뀌지 않습니다. 중요한 리소스는 빠르고 분명해야 하며, 중요하지 않은 리소스는 기다려야 합니다.
Lazy loading은 이 구분을 표현할 때 가치가 있습니다. 페이지가 이미 LCP 경쟁에서 지기 시작한 뒤까지 가장 중요한 콘텐츠를 브라우저로부터 숨길 때는 해롭습니다.