책
모던 리액트 Deep Dive
를 읽으며 정리한 내용입니다.
구글이 만든 지표로 웹 사이트에서 뛰어난 사용자 경험을 제공하는 데 필수적인 지표를 일컫는 용어입니다.
구글에서 핵심 웹 지표로 꼽는 지표는 다음과 같습니다.
- 최대 콘텐츠풀 페인트(LCP: Largest Contentful Paint)
- 최초 입력 지연(FID: First Input Delay)
- 누적 레이아웃 이동(ClS: Cumulative Layout Shift)
아래의 두 지표는 핵심까지는 아니지만 문제를 진단하는데 사용될 수 있다고 언급했습니다.
- 최초 바이트까지의 시간(TTFB: Time To First Byte)
- 최초 콘텐츠풀 시간(FCP: First Contentful Paint)
최대 콘텐츠풀 페인트(LCP)
페이지가 처음으로 로드를 시작한 시점부터 뷰포트 내부에서 가장 큰 이미지 또는 텍스트를 랜더링하는 데 걸리는 시간
=> 사용자의 기기가 노출하는 뷰포트 내부에서 가장 큰 영역을 차지하는 요소가 렌더링되는 데 얼마나 걸리는지를 축정하는 지표
'큰 이미지와 텍스트'란?
<img>
<svg>
내부의<image>
- poster 속성을 사용하는
<video>
- url()을 통해 불러온 배경 이미지가 있는 요소
- 텍스트와 같이 인라인 텍스트 요소를 포함하고 있는 블록 레벨 요소(div, p...)
기준 점수
- ~ 2.5초: 가장 좋음
- ~ 4초: 보통
- 4초 ~: 나쁨
개선 방법
-
뷰포트 최대 영역, 즉 최대 콘텐츠풀 페인트 예상 영역에 이미지가 아닌 문자열을 넣는 것
- 하지만 여러 이해관계자들 사이에서 텍스트로 채울 것을 주장하는 것은 쉽지 않음
-
그렇다면 이미지를 불러오는 방법을 개선할 것
<!-- 1 --> <img src="lcp.jpg" ... /> <!-- 2 --> <svg xmlns="http://www.w3.org/1000/svg"> <img src="lcp.jpg"> </svg> <!-- 3 --> <video poster="lcp.jpg"></video> <!-- 4 --> <div style="background-image: url(lcp.jpg)"></div>
-
<img>
내부의 리소스는 HTML 파싱이 완료되지 않더라도 프리로드 스캐너가 병렬적으로 리소스를 다운로드하므로 최대 콘텐츠풀 페인트 요소를 불러오기에 적절한 방법이다.<picture>
도 마찬가지 -
<svg>
내부의<img>
는 모든 리소스를 다 불러온 이후에 이미지를 불러온다. 프리로드 스캐너에 의해 발견되지 않아 병렬적으로 다운로드가 일어나지 않는다. 이는 결국 최대 콘텐츠풀 페인트 점수에도 악영향을 끼치므로 이 방법은 지양하는 것이 좋다. -
<video>
의 poster는 사용자가 video 요소를 재생하거나 탐색하기 전까지 노출되는 요소다. 이역시 프리로드 스캐너에 의해 발견되어<img>
와 같은 성능을 낸다. -
background-image에서 url(): CSS에 있는 리소스는 항상 느리다. 이러한 리소스는 브라우저가 해당 리소스를 필요로 하는 DOM을 그릴 준비가 될 때까지 리소스 요청이 뒤로 미뤄지기 때문이다. 그러므로 가능하다면 최대 콘텐츠풀 페인트와 같이 중요한 리소스에는 사용하지 않는 것이 좋다.
-
그 외
- 이미지 무손실 압축
- loading=lazy 주의: 리소스를 중요하지 않음으로 표시하고 필요할 때만 로드하는 전략으로, 최대 콘텐츠풀 페인트에 영향이 있는 이미지에 사용하면 로딩 속도를 늦추게 된다.
- fadeIn 같은 각종 애니메이션: 이미지가 그냥 뜨는 것보다 애니메이션으로 표시되는 것이 당연히 최대 콘텐츠풀 페인트이 늦춰진다.
- 클라이언트에서 빌드하지 말 것: 가능한 한 여기 해당하는 영역은 서버에서 미리 빌드된 채로 오는 것이 좋다.
- 최대 콘텐츠풀 리소스는 직접 호스팅: 같은 도메인에서 직접 호스팅하는 것이 좋다. 다른 출처에서 이미지를 가져오면 네트워크 커넥션부터 다시 수행해야 하기 때문이다. 이미지 최적화 서비스가 필요하다고 하더라도 최대 콘텐츠풀 리소스 이외의 이미지에 적용하는 것이 좋다.
최초 입력 지연(FID)
화면이 최초에 그려지고 난 뒤, 사용자가 웹페이지에서 클릭 등 상호작용을 수행했을 때 메인 스레드가 이 이벤트에 대한 반응을 할 수 있을 때까지 걸리는 시간
- 자바스크립트 실행 횐경은 싱글 스레드이기 때문에 자바스크립트가 이벤트 리스터와 같은 다른 작업을 실행할 수 없어 지연이 발생한다. 즉, 이벤트가 발생하는 시점에 최대한 메인 스레드가 다른 작업을 처리할 수 있도록 여유를 만들어 둬야 사용자에게 빠른 반응성을 보장할 수 있다.
- 여기서 사용자 입력은 클릭, 터치, 타이핑 등 사용자의 개별 입력 작업에 초점을 맞춘다.(스크롤, 핀치 투 줌 등은 애니메이션으로 분류하여 측정 대상에서 제외)
기준 점수
- ~ 100ms: 좋은 점수
- ~ 300ms: 보통
- 300ms ~: 나쁨
개선방안
- 실행에 오래 걸리는 긴 작업을 분리
- 개발자도구에서 성능을 확인하고 오래 걸리는 작업들이 꼭 웹페이지에서 해야하는 작업인가 판단
- 꼭 웹 페이지에서 처리해야 하는 작업이라면 해당 작업을 여러 개로 분리하는 것이 좋다.
- 당장 로딩이 필요하지 않은 리소스는 리액트의 Suspense와 lazy를, 혹은 Next.js의 dynamic을 이용해 나중에 불러오게 할 수 있다.
- 자바스크립트 코드 최소화
- 개발자 도구에서 코드의 사용량을 확인해보고 사용되지 않는 코드는 지연로딩기법을 사용하여 사용자가 필요한 순간에 불러오거나 우선순위를 낮춰서 불러오는 것이 좋다.
- 폴리필: 브라우저에서 지원하지 않는 기능을 사용하기 위해 웹페이지에서 직접 구현하고 집어넣는 코드 인터넷 익스플로러 11를 지원하야한다면 폴리필의 코드가 제법 크기 때문에 꼭 폴리필이 필요한 환경인지, 필요한 폴리필인지 확인해보고 넣어야한다. 바벨 같은 도구를 사용하고 있다면 @babel/present-env를 사용해 애플리케이션 코드에서 사용하고 있는 내용만 폴리필에 담을 수 있다. Next.js의 SWC를 사용하고 있다면 이미 내부에 구현되어 있어 별도로 처리하지 않아도 된다.
- 타사 자바스크립트 코드 실행의 지연
- GA 같이 통계를 위해 서드파티 자바스크립트를 넣는 경우가 많다. 이러한 코드들이 중요하긴 하지만 이로 인해 사용자에게 안 좋은 반응성을 제공한다면
<script>
의 defer와 async 속성을 사용하여 지연처리를 하는 것이 좋다.- defer: 다른 리소스와 병렬로 다운로드, 페이지가 완전히 로딩된 이후에 맨 마지막에 실행
- async: 다른 리소스와 병렬로 다운로드, 다운로드가 완료된 순서대로 실행
- 기본: script를 만나는 순간 다운로드, 완료되면 실행된다. 다른 작업이 다운로드와 실행이 끝날 때까지 미뤄진다.
- GA 같이 통계를 위해 서드파티 자바스크립트를 넣는 경우가 많다. 이러한 코드들이 중요하긴 하지만 이로 인해 사용자에게 안 좋은 반응성을 제공한다면
- 광고 같은 사용자의 뷰포트 위치에 따라 불러와야하는 컴포넌트는 Intersection Observer를 이용해 뷰포트에 들어오는 시점에 불러오는 것이 좋다.