LCP 최적화


원문: https://csswizardry.com/2022/03/optimising-largest-contentful-paint/

목차
  1. 미리 모두 해결하기
  2. LCP 후보 최적화하기
  3. 이미지 기반 LCP 피하기
  4. 최적의 후보 사용하기
    1. 데모
    2. img 엘리먼트
      • picture와 source
    3. video 엘리먼트의 poster 속성
    4. background-image: url();
      • background-image 이슈 해결하기
    5. 자기 무덤을 파지 않기
      1. 지연 로드 사용하지 않기
      2. fade-in 하지 않기
      3. 외부 호스팅을 사용하지 않기
      4. 클라이언트에서 LCP 구성하지 않기
      5. 자신의 LCP 빼앗지 않기
    6. 요약

최대 컨텐트풀 페인트(LCP)는 내가 가장 좋아하는 코어 웹 바이탈(역자 주: core web vital)이다. 가장 쉽게 최적화할 수 있으며, 3가지 중에 유일하게 실제와 똑같이 동작한다. 하지만 놀랍게도 크롬UX(CrUX)에서 가장 최적화되지 않은 코어 웹 바이탈이다. 작성 당시 데이터의 절반만 좋은 LCP를 가졌다.

twitter1

LCP는 개선해야 할 가장 간단한 측정 기준이지만 그 효과는 정말 놀랍다. 따라서 이 글에서는 아주 간단한 팁부터 시작해서 몇 가지 흥미로운 트릭과 최적화, 함정과 버그를 자세히 보여주고자 한다.

시작해 보자.

미리 모두 해결하기

쉬운 것부터 시작하자. LCP는 다음을 측정한다.

페이지가 처음 로드되기 시작한 시점을 기준으로 하여 뷰포트 내에서 볼 수 있는 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간

여기서 주목해야 할 중요한 점은, Google은 LCP에 빨리 도착하는 한 사용자가 LCP에 어떻게 도달하는지는 신경 쓰지 않는다는 것이다. 페이지 로드 수명주기의 시작과 LCP 사이에서 발생할 수 있는 다른 일이 많이 있다. 다음 일이 포함된다(이에 국한되지는 않는다).

  • DNS, TCP, TLS 통신
  • 리다이렉트
  • TTFB
  • 최초 페인트
  • 최초 컨텐트풀 페인트

이 중 하나라도 느리다면 이미 뒤로 가기를 누르고 있으며 LCP에 영향을 미칠 것이다. 위의 측정 기준들 자체가 중요하지는 않지만, 지표를 참고한다면(가지고 있다면) LCP를 최대한으로 낮추는 데 도움이 될 것이다.

Treo

Treo는 크롬UX에서 타이밍 데이터를 가져오기 위한 도구이다.

기술과 관련 없는 이해관계자(non-technical stakeholders)에게 사용하는 비유는 다음과 같다:

8시 30분까지 아이들을 학교에 보내야 한다. 학교에서는 아이들이 제시간에 도착하는 것에만 신경을 쓴다. 이를 위해 많은 일을 할 수 있다. 전날 밤에 옷을 준비하거나 전날 저녁에 점심을 준비한다(당신 것도 준비해라). 적절한 알람을 설정한다. 모두가 따르는 아침 루틴을 가져라. 충분한 시간적 여유를 두고 집을 나서라. 교통 문제 등에 대한 적절한 예비 시간을 계획해라 등등.

학교는 전날 밤에 교복을 준비했는지 신경 쓰지 않는다. 아이들을 제시간에 학교에 데려다주는 능력을 평가한다. 아이들을 데려다 주기 위해 할 수 있는 한 많은 일을 해야 한다.

LCP도 마찬가지다. Google은 (현재)TTFB에 신경쓰지 않지만 좋은 TTFB는 좋은 LCP가 되는 데 도움이 될 것이다.

전체 과정을 최적화해라. 성공을 위한 준비를 할 수 있도록 가능한 한 빨리 모든 것을 사전에 준비해라.

LCP 후보 최적화하기

이번 팁은 필자에게는 필요하지 않아서 자세한 사례가 없다. 만약, 이미지 기반 LCP가 있는 경우 적절한 형식을 가졌는지, 적절한 크기인지, 적절히 압축되었는지 등을 잘 확인하고 최적화하자. LCP 후보에 3MB TIFF를 포함시키지 말자.

이미지 기반 LCP 피하기

대부분의 사이트에는 적용되지 않을 것이다. 그러나 빠른 LCP를 얻는 가장 좋은 방법은 LCP가 텍스트 기반인지 확인하는 것이다. 이는 사실상 FCP와 LCP를 같게 만든다12. 이게 끝이다. 간단하다. 가능하면 이미지 기반 LCP 후보를 피하고 텍스트 LCP를 선택해라.

그러나 효과가 없을 것이다. 다른 선택지를 살펴보자.

최적의 후보 사용하기

이제 재미있어질 거다. 어떤 LCP 후보가 있는지, 그리고 각각에 대해 어떤 상대적인 장점이 있는지 살펴보자.

LCP에 대한 몇 가지 잠재적 후보가 있다. web.dev의 LCP 페이지에서 가져온 다음 후보들이 있다.

  • <img> 요소
  • <svg>안의 <image> 요소
  • <video> 요소 (poster 이미지 사용)
  • url() 함수를 이용해 배경 이미지를 로딩하는 요소 (CSS 그래디언트와 반대)
  • 텍스트 노드나 다른 인라인 레벨의 텍스트 요소 자식을 포함하는 블록 레벨 요소

데모

이 글의 목적을 위해 각 LCP 유형들이 어떻게 동작하는지 보여주는 간단한 데모를 만들었다. 각 데모에는 다음을 수행하기 위해 <head> 안의 자바스크립트 파일 차단에 대한 참조도 포함되어 있다.

  1. 워터폴(waterfall)을 늘린다.
  2. 각 LCP 유형이 프리로드 스캐너(Preload scanner)에 의해 영향을 받는지 또는 어떻게 영향을 받는지 알아보기 위해 파서를 정지한다.

또한 각 데모는 매우 많이 생략되었으며 많은 응답이 동시에 진행되는 현실적인 조건을 나타내지는 않는다. 리소스 경합이 발생하면 LCP 후보의 발견이 이런 축소된 데모에 표시된 것과 다르게 동작할 수 있다. 이런 경우 우선순위 힌트 또는 프리로드를 통해 도움을 받을 수 있다. 지금 관심사는 브라우저가 특정 리소스를 처리하는 방식의 본질적인 차이다.

초기 데모는 다음과 같다.

<img src="lcp.jpg" ... />
<svg xmlns="http://www.w3.org/1000/svg">
  <image href="lcp.jpg" ... />
</svg>
<video poster="lcp.jpg" ...></video>
<div style="background-image: url(lcp.jpg)">...</div>

WebPageTest 비교를 통해 살펴볼 수 있지만 글 뒷부분에서 개별 워터폴을 선택할 것이다. 다음과 같이 나타난다.

web page test

<svg>안에 <image>가 있는 LCP의 버그에 유의하자. 이는 나중에 자세히 설명한다. (전체 크기로 보기)

<img>poster는 LCP가 동일하다. 크롬 리포트의 LCP 시간에 버그가 있지만 <svg>안의 <image>가 가장 빨랐고, background-image 기반 LCP가 특히 가장 느렸다.

LCP Timing by Type

101 버전 이하의 크롬에서는 텍스트 노드를 LCP 요소로 잘못 보고하는 버그가 있다. 102버전에서 고쳐진다.

보다시피, 모든 후보가 같지 않다. 각각에 대해 더 자세히 살펴보자.

<img> 요소

img element

LCP 후보가 즉시 발견되었다.

이미지 기반 LCP 중 이 결과는 아마 우리가 가장 좋아할 것이다. <img> 요소는 프리로드 스캐너에 의해 빠르게 발견되기 때문에 선행 리소스와 병렬로 요청할 수 있으며 차단할 수도 있다.

<picture><source />

<picture> 요소가 <img /> 요소와 같은 방식으로 동작한다는 것에 주목하자. 따라서 srcsetsizes 속성에 대한 자세한 구문을 작성해야 한다. 이 아이디어는 브라우저에 이미지에 대한 충분한 정보를 제공해서 프리로드 스캐너를 통해 관련 파일을 요청할 수 있고 레이아웃까지 기다릴 필요가 없도록 만든다. (기술적으로 <source />, srcset, sizes의 조합을 결정하는 데 몇 밀리초 정도의 연산 오버헤드가 있지만, 함께 움직이는 다른 부분에 의해 더 느려질 것이다.)

<svg> 안의 <image>

<svg>안에 정의된 <image> 요소는 두 가지 흥미로운 동작을 한다. 첫 번째는 크롬이 LCP 후보를 잘못 보고하는 간단한 버그로 <image>를 전혀 고려하지 않는 것처럼 보인다. 특정 상황에서는 이 버그로 인해 LCP 점수가 훨씬 좋아질 것이다.

M99

작성 당시 101버전 미만의 크롬에서는 보고된 LCP가 <image> 요소가 아닌 것으로 보고되는 버그가 있다. 데모에서는 실제로 훨씬 작은 <p> 요소로 플래그가 지정된다.

수정 사항이 M102(작성 당시 Canary이며 2022년 5월 24일에 안정 버전에 도달)에 배포되면 정확한 측정을 기대할 수 있다. 이는 LCP 점수가 떨어질 수 있음을 의미한다.

M102

크롬 102버전에서 버그가 수정됬다.

현재 버그로 인해 <svg>안의 <image>는 가장 빠른 LCP 유형에서 가장 느린 LCP 유형이 될 가능성이 높다. <svg>안의 <image>를 사용하는 경우는 드물지만 나중에 확인하고 싶은 항목일 수 있다. 점수가 바뀔 가능성이 높다.

버그는 보고된 LCP 후보에만 관련되며 브라우저가 실제로 리소스를 처리하는 방식에는 영향을 미치지 않는다. 이를 위해 모든 크롬 버전의 워터폴은 동일하게 보이고 네트워크/스케줄 동작은 변경되지 않는다. <svg><image>에서 발견한 두 번째 흥미로운 사실은 다음과 같다.

hidden

LCP 후보는 프리로드 스캐너에서 숨겨진다.

<svg><image> 요소는 프리로드 스캐너에서 숨겨져 있는 것처럼 보인다. 즉, href 속성은 브라우저의 기본 파서가 이를 만날 때까지 파싱 되지 않는다. 프리로드 스캐너가 SVG가 아닌 HTML을 스캔하도록 만들어졌기 때문이며 이는 실수가 아닌 의도적으로 설계되었다고 추측할 수 있다. 아마도 크롬이 해볼 수 있는 최적화는 HTML에 포함된 SVG를 미리 로드하는 방법 정도일 것이다. 다만, 필자의 말처럼 그렇게 쉽지는 않을 것이라고 예상한다.

<video> 요소의 poster 속성

<video>poster 속성이 보여주는 동작에 놀랐다. <img /> 요소와 동일하게 동작하는 것으로 보이며 프리로드 스캐너에 의해 조기에 발견된다.

poster

즉시 발견되는 LCP 후보

이는 poster LCP가 본질적으로 매우 빠르다는 것을 의미한다.

또 다른 점은 poster가 없을 경우 동영상의 첫 번째 프레임을 LCP 후보로 하려는 의도가 보인다는 점이다. 이는 영상이 2.5초 미만인 경우 얻기 어려운 LCP가 될 것이므로 <video> LCP가 전혀 없거나 poster 이미지를 사용하여 시작해야 한다.

background-image: url();

background-image

관련 DOM 노드가 파싱 될 때 발견된 LCP 후보(동기 JS에 의해 차단됨).

CSS에 정의된 리소스(주로 url() 함수를 통해 요청된 모든 것)는 기본적으로 느리다. 가장 일반적인 후보는 배경 이미지와 웹 글꼴이다.

이런 리소스(이 경우엔 배경 이미지)가 느린 이유는 브라우저가 이를 필요로 하는 DOM 노드를 그릴 준비가 될 때까지 요청되지 않기 때문이다. 이에 대한 자세한 내용은 이 Twitter에서 확인할 수 있다.

twitter2

이는 배경 이미지 LCP가 마지막 순간에 요청된다는 것을 의미한다. 너무 늦다.

background-image 이슈 해결하기

현재 LCP가 배경 이미지인 사이트가 있는 경우 당장 해당 컴포넌트를 리팩토링하거나 다시 만드는 것을 생각해 볼 수 있다. 하지만 별다른 노력이 필요 없는 빠른 해결 방법이 있다. 브라우저가 훨씬 일찍 발견할 수 있는 숨겨진 <img />로 배경을 보완해 보자.

<div style="background-image: url(lcp.jpg)">
  <img src="lcp.jpg" alt="" width="0" height="0" style="display: none !important;" />
</div>

이 작은 꼼수를 통해 브라우저가 <div>를 렌더링 할 때까지 기다리지 않고 프리로드 스캐너가 이미지를 선택할 수 있다. 단순한 background-image 구현보다 1.058초 빨랐다. 이 워터폴은 가장 빠른 <img /> 옵션을 거의 정확히 모방하는 것을 알 수 있다.

background-image waterfall

<img /> 요소를 사용하는 대신 이 이미지를 preload할 수도 있지만 일반적으로 preload는 가능하면 피해야 할 코드라고 생각한다.

요약

  • 텍스트 기반 LCP는 거의 항상 가장 빠르다.
  • <img />poster LCP는 빠르며 프리로드 스캐너에서 검색할 수 있다.
  • poster가 없는 video는 향후 크롬 버전에서 첫 번째 프레임이 LCP 후보로 간주될 수 있다.
  • <svg><image>는 현재 잘못 보고되고 있지만 href가 프리로드 스캐너에서 숨겨져 있어 느리다.
  • CSS 동작 방식 때문에 background-image는 기본적으로 느리다.

    • 보이지 않는 <img />를 추가해 문제를 해결할 수 있다.

자기 무덤을 파지 않기

괜찮다! 이제 최고의 후보가 뭔지 알고 있다. 빠르게 하기 위해 할 수 있는(또는 피하는) 다른 방법이 있을까? 사람들이 무심코 LCP 점수를 떨어뜨리는 많은 행위들이 있다는 것이 밝혀졌다.

당신의 LCP를 지연 로드로 사용하지 않기

이걸 볼 때마다 가슴이 철렁 내려앉는다. LCP를 지연 로드하는 것은 직관적이지 않다. 제발 하지 마라!

lazy-load

흥미롭게도 loading="lazy" 기능 중 하나는 이미지를 프리로드 스캐너에서 숨긴다는 것이다. 즉, 이미지가 뷰포트에 있더라도 브라우저는 여전히 늦게 요청한다. 이렇게 동작하는 이유는 당신이 모든 이미지들애 안전하게 loading="lazy"를 추가할 수 없으며 브라우저가 (당신이 생각하는 대로) 올바르게 작업을 수행하길 희망하기 때문이다.

테스트에서 이미지를 지연 로드하면 LCP가 4.418초로 되돌아간다. <img /> 변형보다 1.274초 느리고 background-image 테스트와 거의 동일하다.

fade-in 하지 않기

예상한 대로 이미지가 500ms 이상 fade-in 되면 LCP 이벤트가 500ms 이상 뒤로 밀려난다. 크롬은 애니메이션 기간의 끝을 LCP 측정으로 사용하여 3.144초가 아닌 3.767초 LCP 이벤트로 이동한다.

fade-in

이미지는 3.5초에 도착하지만 LCP는 4초에 보고된다.

이미지 기반이든 텍스트 기반이든 LCP 후보의 fade-in을 피해라.

외부 호스팅을 사용하지 않기

가능하면 항상 정적 자산을 자체 호스팅 해야 한다. 여기에는 LCP 후보도 포함된다.

사이트 소유자가 Cloudinary 같은 써드 파티 이미지 최적화 서비스를 사용하여 자동 및 동적으로 최적화된 이미지(즉석 크기 조정, 형식 전환, 압축 등)를 모두 제공하는 것은 드문 일이 아니다. 그러나 이런 서비스의 성능 향상을 고려하더라도 다른 오리진으로 이동하는 비용이 거의 항상 이점보다 크다. 테스트에서 새 오리진을 확인하는 데 걸린 시간은 LCP 이미지를 다운로드하는 데 소요된 전체 시간에 509ms 더 오래 걸렸다.

반드시 중요하지 않고 LCP가 아닌 이미지에 대해서는 써드 파티 서비스를 사용해라. 그러나 가능하다면 LCP 후보를 호스팅 페이지와 동일한 오리진으로 가져오라. 이것이 바로 내가 이 사이트에서 하는 일이다.

주의: preconnect가 약간의 도움이 될 순 있지만 새 연결을 전혀 열지 않는 것보다 빠르지는 않을 것이다.

클라이언트 단에서 빌드하지 않기

난 이걸 매우 자주 보았고 이는 자바스크립트에 대한 계속되는 집착의 일부다. 이상적으로는 브라우저가 HTML 응답을 수신하고 LCP 후보(이상적으로는 <img /> 요소)에 대한 참조가 즉시 표시된다. 하지만 자바스크립트로 LCP 후보를 구성하면 프로세스가 훨씬 더 복잡해진다.

자바스크립트에서 LCP 후보 빌드는 단순한 자바스크립트 기반 이미지 갤러리에서 완전한 클라이언트 렌더링 페이지에 이르기까지 다양하다. 아래 워터폴은 후자를 보여준다.

build-client

첫 응답은 HTML이다. 우리가 원하는 것은 마크업 안의 <img />이며 즉시 발견되기를 기다리고 있다. 대신 HTML은 12번 항목에서 지연framework.js를 요청한다. 결국 50번 항목에서 현재 제품에 대한 API 데이터를 요청한다. 이 응답에는 관련 제품 이미지에 대한 정보가 포함되어 있으며, 결국 가상 DOM에 <img />로 삽입되어 페이지 로드 수명주기의 7초가 훨씬 넘는 53번 항목에서 LCP 후보에 대한 요청을 시작한다.

자신의 LCP 빼앗지 않기

매번 볼 때마다 마음이 아프다. 실수로 LCP 후보가 된 컨텐츠를 늦게 로드하지 말아라. 일반적으로 컨텐츠를 덮고 있고 매우 늦은 LCP로 표시되는 쿠키 배너 또는 뉴스레터 모달과 같은 항목이다. 테스트를 위해 늦게 로드되는 모달을 모킹했고, 중요한 점은 점수가 정확하지만 우리가 기대했던 것과 다르다는 점이다.

usurp-lcp

LCP 후보가 기대하는 것과 같은지 확인하자. 다음을 고려해서 모달 및 쿠키 배너 등을 디자인해라.

  1. 즉시 로드하자.
  2. 가장 큰 컨텐트가 되지 않게 하자.

요약

이 글에서 꽤 많은 것을 다뤘지만 핵심은 매우 간단하다. 텍스트 기반 LCP가 가장 빠르지만 대부분의 경우 가능하지 않다. 이미지 기반 LCP 유형 중 <img />poster가 가장 빠르다. <svg>에 정의된 <image>는 프리로드 스캐너에서 숨겨져 있기 때문에 느리다. 그 외에도 피해야 할 사항이 몇 가지 있다. LCP 후보를 지연 로드 하지 말고, 자바스크립트에서 LCP를 구성하지 않아야 한다.

LCP-candidate

전체 크기로 보기

1. 초기의 보이지 않는 텍스트 그리기를 피하기 위해 font-display: [swap|optional]을 사용하고 있는지 확인해야 한다.
2. 이를 조사하는 동안 또 다른 버그를 발견했다.