핵심 웹 지표(Core Web Vitals) 정리

모던 리액트 Deep Dive 12장 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표를 읽고 정리한 내용입니다.

 

사용자가 웹사이트에 접속했을 때 공통적으로 기대하는 사항

  • 방문한 목적을 손쉽게 달성할 수 있어야 한다.
  • 목적을 달성하는 데 걸리는 시간이 짧아야 한다.
  • 개인정보가 누출되는 등의 사고 없이 보안이 철저해야 한다.

즉, 아무리 좋은 기술 스택을 쓰더라도 사용성이 불편하면 사용자는 해당 서비스를 사용하지 않습니다. 따라서 개발자는 웹사이트의 성능에 주의를 기울여야 합니다.

 

과거 웹사이트의 성능은 단순히 웹사이트의 로딩 속도, 방문이나 결제 같은 실제 목표로 이뤄지는 전환율 등으로만 지엽적으로 판단했습니다. 그러나 몇 년전부터 구글은 핵심 웹 지표(Core Web Vital)라고 하는 웹사이트의 우수한 사용자 경험을 제공하는 데 필요한 몇 가지 핵심적인 요소를 꼽고 이에 대한 지표를 제시하고 있습니다.

 

구글에서 핵심 웹 지표로 선택한 지표는 아래와 같다.

  • 최대 콘텐츠풀 페인트(LCP: Largest Contentful Paint)
  • 최초 입력 지연(FID: First Input Delay)
  • 누적 레이아웃 이동(CLS: Cumulative Layout Shift)

Largest Contentful Paint(LCP)

페이지가 처음으로 로드를 시작한 시점부터 뷰포트 내부에서 가장 큰 이미지 또는 텍스트를 렌더링하는 데 걸리는 시간을 의미합니다. 사용자는 뷰포트 영역을 기준으로 페이지 로딩이 완료됐다고 느낍니다.

 

가장 큰 이미지 또는 텍스트

  • <img>
  • <svg> 내부의 <image>
  • poster 속성을 사용하는 <video>
  • url()을 통해 불러온 배경 이미지가 있는 요소
  • 텍스트와 같이 인라인 텍스트 요소를 포함하고 있는 블록 레벨 요소
뷰포트
사용자에게 현재 노출되는 화면을 의미합니다. 사용자에게 노출되는 영역은 기기에 의존하므로 뷰포트 크기는 기기마다 다릅니다.

이미지와 텍스트가 각각 사용자의 시점에 언제 보여지는지 확인하는 정확한 시점은 각 엘리먼트가 등장한 시점부터 텍스트 또는 이미지가 완전히 로딩되는 시점으로 보면 됩니다.

 

즉, LCP는 사용자의 기기가 노출하는 뷰포트 내부에서 가장 큰 영역을 차지하는 요소가 렌더링되는 데 얼마나 걸리는지를 측정하는 지표입니다. 실제 크기가 크다고 하더라도 뷰포트 영역 밖에 넘치는 요소가 있다면 해당 영역의 크기는 고려되지 않습니다.

 

LCP에서 좋은 점수란 해당 지표가 2.5초 내로 응답이 오는 것입니다. 4초 이내면 보통, 그 이상 걸리면 나쁨으로 판단합니다.

개선 방안

  • LCP 예상 영역에 가능하면 이미지가 아닌 텍스트를 넣는다.
  • 프리로드 스캐너에 의해 조기 발견될 수 있는 방식을 사용한다. 프리로드 스캐너란 HTML을 파싱하는 단계를 차단하지 않고 이미지와 같이 빠르게 미리 로딩하면 좋은 리소스를 먼저 찾아 병렬로 로딩하는 브라우저 기능이다.
  • CSS에 있는 리소스는 항상 느리다. 해당 리소스는 브라우저가 해당 리소스를 필요로 하는 DOM을 그릴 준비가 될 때까지 리소스 요청을 미룬다.
  • 이미지를 가능하면 무손실 형식으로 압축해 용량을 최소화 한다.
  • 최대 콘텐츠풀 콘텐츠의 이미지에는 Lazy 로딩을 사용하지 않는 것이 좋다.
  • LCP 영역은 클라이언트에서 빌드하지 않는다. 서버에서 미리 빌드된 채로 오는 것이 좋다.
  • 최대 콘텐츠풀 리소스는 직접 호스팅한다. 완전히 새로운 출처의 경우에는 네트워크 커넥션부터 다시 수행해야 한다.

First Input Delay(FID)

사용자가 페이지와 처음 상호 작용할 때부터 해당 상호 작용에 대한 응답으로 브라우저가 실제로 이벤트 핸들러 처리를 시작하기까지의 시간을 측정합니다. 페이지가 빨리 로딩된다고 해도 상호작용을 할 수 없다면 사용자는 웹사이트가 느리다고 생각할 것입니다.

 

자바스크립트는 싱글 스레드이기 때문에 대규모 렌더링이 일어나거나 대규모 자바스크립트 파일을 분석하고 실행하는 등 다른 작업을 처리하는 데 메인 스레드의 리소스를 할애하고 있을 경우 이벤트 처리가 늦어질 수 있습니다.

 

따라서 이벤트가 발생하는 시점에 최대한 메인 스레드가 다른 작업을 처리할 수 있도록 여유를 만들어 둬야 사용자에게 빠른 반응성을 보장할 수 있습니다. FID는 사용자 입력 중 클릭, 터치, 타이핑 등 개별 입력 작업에 초점을 맞추고 측정합니다.

RAIL
구글은 사용자 경험을 크게 4가지로 분류해 정의한다.
Response : 사용자 입력에 대한 대한 반응 속도, 50ms 미만으로 이벤트를 처리할 것
Animation : 애니메이션의 각 프레임을 10ms 이하로 생성할 것
Idle : 유휴 시간을 극대화해 페이지가 50ms 이내에 사용자 입력에 응답하도록 할 것
Load : 5초 이내에 콘텐츠를 전달하고 인터랙션을 준비할 것

최초 입력 지연은 Response에 해당하는 응답에 초점을 맞추고 있다.

FID의 좋은 점수는 100ms 이내로 응답이 와야 하며, 300ms 이내인 경우 보통, 그 이후의 경우 나쁨으로 처리됩니다.

개선 방안

  • 메인 스레드를 오래 점유하는 긴 작업은 브라우저가 아닌 서버에서 처리하거나 여러 개로 분리하여 처리한다. 크롬의 경우 50ms 이상이면 오래 걸리는 작업이라고 간주한다.
  • 당장의 로딩에 필요하지 않는 리소스는 Lazy 로딩한다. 리액트의 Suspense와 lazy 혹은 Next.js의 dynamic을 사용한다.
  • 자바스크립트 코드를 최소화 한다.
  • 꼭 필요한 폴리필만 포함시킨다. @babel/preset-env를 사용해 애플리케이션 코드에서 사용하고 있는 내용만 프로필에 포함할 수 있다. Next.js의 SWC를 사용하고 있는 경우 SWC 내부에 구현돼 있기 때문에 별도의 처리가 필요없을 수도 있다.
  • async와 defer를 이용해 타사 자바스크립트 코드의 실행을 지연시킨다. 실제 사용자의 뷰포트 위치에 따라 불러와야 하는 컴포넌트라면 뷰포트에 들어오는 시점에 불러오는 것이 좋다.

Cumulative Layout Shift(CLS)

페이지의 생명주기 동안 발생하는 예기치 않은 이동에 대한 지표를 계산하는 것을 의미합니다. 사용자가 겪는 예상치 못한 레이아웃 이동이 적을수록 더 좋은 웹사이트입니다.

 

누적 레이아웃 이동은 사용자의 가시적인 콘텐츠에 영향을 미쳐야 하기 때문에 뷰포트 내부의 요소에 대해서만 측정하며, 뷰포트 밖의 요소에 대해서는 측정하지 않습니다. 또한, 요소가 추가됐다 하더라도 다른 요소의 시작 위치에 영향을 미치지 않았다면 레이아웃 이동으로 간주되지 않습니다.

 

계산할 때 포함되는 내용

  • 영향분율 : 레이아웃 이동이 발생한 요소의 전체 높이와 뷰포트 높이의 비율을 의미한다.
  • 거리분율 : 레이아웃 이동이 발생한 요소가 뷰포트 대비 얼마나 이동했는지를 의미한다.

이 두 가지 점수를 곱해서 최종 점수를 계산합니다. 0.1 이하인 경우 좋음, 0.25 이하인 경우 보통이며 그 외에는 나쁜 점수입니다. 개인적인 생각으로 누적 레이아웃 이동의 지표는 기기의 화면이 작을수록 좋은 점수를 받을 수 있을 가능성이 높을 수 있습니다. 따라서 지표에 의존하기보다는 현상을 해결하는 것이 좋은 방법이라고 생각합니다.

개선 방안

  • 스켈레톤 UI 등으로 요소가 동적으로 삽입될 공간을 확보한다. 그러나 삽입 실패시 이 방법도 레이아웃 이동이 발생할 수 있다.
  • 신중히 검토 후 useLayoutEffect를 사용한다. useLayoutEffect는 동기적으로 발생해 브라우저의 페인팅 작업에 영향을 미치기 때문에 사용자에게 로딩이 오래 걸리는 것과 같이 보일 수 있다.
  • 서버 사이드 렌더링
  • 폰트 최적화를 통해 FOUT 또는 FOIT 현상을 최소화한다.
  • css 속성과 함께 <img>의 width, height를 원하는 비율로 지정한다. 이렇게 하면 aspect-ratio 속성으로 인해 브라우저가 이미지를 로딩하기 전에 적절한 가로세로 비율을 계산해 이미지가 표시되는 만큼 면적을 할당한다.
<img 
  src="파일 경로" 
  width="1600" 
  height="900" 
  style={{ width: '100%', height: 'auto' }} 
/>

// 반응형 이미지를 사용하고 싶다면 srcset 속성을 사용
<img 
  src="파일 경로" 
  width="1000" 
  height="1000"
  srcset="image-1000.jpg 1000w, image-2000.jpg 2000w, image-3000.jpg 3000w" 
  style={{ width: '100%', height: 'auto' }} 
/>

'개발' 카테고리의 다른 글

Storybook 알아보기  (0) 2024.06.11
JSON Server와 LowDB, JSON Server Auth로 가상의 백엔드 서버 구현  (0) 2024.02.13