책정리/모던 리액트 Deep Dive

12장 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표

뽀글보리 2024. 6. 28. 10:17
반응형

12.1 웹사이트와 성능

시작하며

사용자는 속도가 빠르고, 보안이 좋으며, 사용성이 좋은 웹사이트를 기대합니다. 그러나 많은 개발자들은 이러한 성능에 크게 신경 쓰지 않는 경우가 많습니다.

  • 개발자의 기기는 대부분 일반적인 사용자의 평균적인 기기보다 성능이 뛰어나기 때문에 이러한 문제를 느끼지 못할 수 있습니다.
  • 성능을 개선하는 작업은 어렵고 재미있지 않으며, 눈에 띄는 성능 향상을 기대하기 어렵습니다.

이번 포스팅에서는 웹 개발자가 관심을 가지며 개발해야할 핵심 웹 지표에 대해 설명합니다.

12.2 핵심 웹 지표란?

이전까지 성능 측정을 위한 뚜렷한 표준이나 측정 방법이 정해져 있지 않았으나, 구글에서 만든 Core Web Vitals가 등장했습니다. 주요 지표는 다음과 같습니다:

  • 최대 콘텐츠풀 페인트 (LCP)
  • 최초 입력 지연 (FID)
  • 누적 레이아웃 이동 (CLS)

이 외에도 문제 진단에 유용한 지표들로는는 다음이 있습니다:

  • TTFB: Time to First Byte, 브라우저가 웹페이지의 첫 번째 바이트를 수신하는 데 걸리는 시간
  • FCP: First Contentful Paint, 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될 때까지의 시간

* 더 자세한 내용은 Core Web Vitals 공식 문서를 참고

12.3 최대 콘텐츠풀 페인트 (LCP)

12.3.1 정의

LCP: Largest Content Paint

페이지가 처음 로드되기 시작한 시점부터 뷰포트 내부에서 가장 큰 이미지나 텍스트를 렌더링하는 데 걸리는 시간입니다. 뷰포트는 사용자에게 현재 노출되는 화면으로, 기기에 따라 크기가 달라질 수 있습니다.

 

큰 이미지와 텍스트로 고려되는 요소는 다음과 같습니다:

  • img
  • svg 내부의 img
  • poster 속성을 사용하는 video
  • URL을 통해 불러온 배경 이미지가 있는 요소
  • 텍스트와 같은 인라인 텍스트 요소를 포함하고 있는 블록 레벨 요소

이 때, 첫 뷰포트 영역 밖에 넘치는 요소의 크기는 고려되지 않습니다.

12.3.2 의미

웹페이지 로딩이 완료되는 시점을 측정하기 위해서는 무엇을 기준으로 측정하면 어떨까요?

가장 처음으로는 DOMContentLoaded 이벤트가 실행되기 까지의 시간을 측정해볼 수 있을 것입니다. 그렇지만 DOMContentLoaded 이벤트는 HTML 문서를 완전히 불러오고 파싱했을 때 발생하고, 스타일시트/이미지/하위 프레임의 로딩은 기다리지 않습니다. 뷰포트 영역의 대부분이 이미지로 이루어져 있다면, DOMContentLoaded 이벤트가 발생했더라도 사용자가 체감하는 로딩 시간과는 차이가 있을 수 있습니다.

LCP는 사용자가 실제로 페이지를 볼 수 있는 시점까지 걸리는 시간을 측정하므로, DOMContentLoaded 이벤트와는 차이가 있으며, 사용자가 페이지가 로드되었다고 체감하는 시간과 더 유사합니다.

12.3.4 기준 점수

  • Good: 2.5초 이하
  • Needs Improvement: 2.5초 ~ 4초
  • Poor: 4초 이상

12.3.5 개선 방안

  1. 텍스트 우선 로드
    • 최초 뷰포트 영역에는 이미지보다 텍스트를 우선적으로 넣는 것이 훨씬 빠릅니다.
  2. 이미지 로딩 최적화
    • 이미지 로딩은 어떤 html 요소를 사용하는가에 따라 다른 방식으로 처리됩니다. 
      • img: 브라우저의 프리로드 스캐너에 의해 먼저 발견되어 빠르게 요청됩니다.
      • svg 내부의 img: 크롬 102 이하 버전에서 svg만 로딩된 시점에 LCP가 완료된 것으로 간주하는 버그가 있습니다.
      • video의 poster:  img태그와 비슷하게 프리로드 스캐너에 의해 동작하며, poster를 사용하지 않으면 비디오의 첫 프레임을 로딩하여 적용합니다.
      • CSS background-image: 리소스 요청을 DOM이 준비될 때까지 미룹니다.
    • 따라서 svg내부에서 img 태그를 사용하는 방법을 삼가하고, img 태그를 사용하는 것이 좋습니다.
  3. 그 밖에 고려해야 할 사항
    • 이미지 무손실 압축: 이미지를 압축하여 최소하의 용량으로 서비스하는 것이 좋습니다.
    • lazy loading: 최대 콘텐츠풀 이미지는 lazy loading을 적용해서는 안 됩니다.
    • 최대 콘텐츠풀 이미지에 애니메이션을 적용하지 않습니다.
    • 클라이언트에서 빌드하지 말고 서버에서 미리 빌드된 상태로 제공하는 것이 좋습니다.
    • 같은 도메인에서 직접 호스팅하는 것이 좋습니다.
// lazy loading을 사용하지 않는다.
<img src="large-image.jpg" alt="Large Image" loading="eager">

12.4 최초 입력 지연 (FID)

12.4.1 정의

FID: First Input Delay

사용자가 페이지에서 처음 상호작용할 때 브라우저가 실제로 이벤트 처리를 시작하기까지의 시간을 측정합니다.

12.4.2 의미

이벤트 반응이 늦어지는 이유는 브라우저의 메인 스레드가 바쁘기 때문입니다. 자바스크립트 실행 환경은 싱글 스레드이기 때문에, 대규모 렌더링이나 파일 실행과 같은 다른 작업을 처리하고 있다면 지연이 발생합니다. 여기에서 입력이란 클릭, 터치, 타이핑을 말하며, 스크롤이나 핀치 투 줌은 입력이 아닌 애니메이션으로 분류되어 측정 대상에서 제외됩니다.

12.4.4 기준 점수

  • Good: 100ms 이하
  • Needs Improvement: 100ms ~ 300ms
  • Poor: 300ms 이상

12.4.5 개선 방안

  1. 긴 작업 분리
    • 꼭 웹페이지에서 해야 하는 작업인가? 서버에서 처리하여 브라우저의 메인 스레드를 오래 점유하지 않게 합니다.
    • 긴 작업을 분리하여 실행이 오래 걸리는 작업을 나중에 불러옵니다. (예: suspense, lazy, dynamic)

코드 예시: 긴 작업 분리 (React Suspense 사용)

import React, { Suspense } from 'react';
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

export default function Page() {
  return (
    <div>
      <h1>페이지 제목</h1>
      <Suspense fallback={<div>로딩 중...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}
  1. 자바스크립트 코드 최소화
    • 번들링 도구를 사용하여 필요한 코드만 포함하도록 합니다.
    • 폴리필 사용을 최소화하고, 필요한 경우에만 사용합니다.

폴리필 사용 시 확인 사항

폴리필은 최신 자바스크립트 기능을 지원하지 않는 브라우저에서 이러한 기능을 사용할 수 있도록 해주는 코드입니다. 예를 들어, Promisefetch와 같은 기능이 오래된 브라우저에서 동작하도록 폴리필을 사용합니다. 그러나 폴리필 사용 전에 다음 사항을 확인해야 합니다:

  • 폴리필이 필요한 환경인가? 인터넷 익스플로러를 지원하지 않는다면, 대부분의 경우 폴리필이 필요하지 않습니다.
  • 꼭 필요한 폴리필인가? 자주 사용하는 코드인지 확인이 필요합니다. 자주 사용하지 않는다면 폴리필을 사용하기보다 직접 자바스크립트 코드를 구현하는 것이 더 나을 수 있습니다.
  • babel/preset-env, Next.js SWC를 사용하고 있다면 이미 구현되어 있는지 확인합니다. 최신 자바스크립트 문법을 구 버전 브라우저에서 동작하도록 변환해주는 Babel이나 SWC를 사용하고 있다면, 필요한 폴리필이 이미 처리되어 있을 수 있습니다.
  1. 타사 자바스크립트 코드 실행 지연
    • google analytics와 같은 타사 스크립트는 지연 불러오기를 사용합니다.
    • defer 및 async 속성을 사용하여 리소스를 병렬로 다운로드하고, 페이지 로딩에 방해되지 않도록 합니다.

defer와 async의 차이

  • defer: 스크립트를 병렬로 다운로드하며, 페이지가 완전히 로딩된 후 마지막에 실행됩니다.
  • async: 스크립트를 병렬로 다운로드하며, 다운로드가 완료된 순서대로 실행됩니다.
<script src="example.js" defer></script>
<script src="example.js" async></script>

12.5 누적 레이아웃 이동 (CLS)

12.5.1 정의

CLS: Cumulative Layout Shift

웹페이지의 생명주기 동안 발생하는 모든 예기치 않은 레이아웃 이동을 의미합니다.

12.5.2 의미

  • 최초 렌더링이 시작된 위치에서 레이아웃 이동이 발생하면 누적 레이아웃 이동 점수로 기록됩니다.
  • 요소가 추가되더라도 다른 요소의 시작 위치가 달라지지 않으면 레이아웃 이동이 아닙니다.
  • 사용자 액션으로 발생한 레이아웃 이동은 점수에 포함되지 않습니다.
  • 계산방법 = 영향분율 * 거리분율
    • 영향분율: 레이아웃 이동 발생한 요소의 전체 높이와 뷰포트 높이의 비율
    • 거리분율: 뷰포트 대비 얼마나 이동했는지
    • 뷰포트가 100이고 10높이의 요소가 10만큼 내려갔다면 각각 0.2, 0.1

12.5.4 기준 점수

  • Good: 0.1 이하
  • Needs Improvement: 0.1 ~ 0.25
  • Poor: 0.25 이상

12.5.5 개선 방안

  1. 삽입이 예상되는 요소를 위한 추가적인 공간 확보
    • useEffect가 아닌 useLayoutEffect 훅을 사용합니다.
    • 스켈레톤을 사용하여 예상되는 공간을 미리 확보합니다.
    • 서버 사이드 렌더링을 통해 동적 요소를 사전에 판단합니다.
  2. 폰트 로딩 최적화

font-family display 속성

  • auto: 브라우저에서 결정합니다.
  • block: 폰트가 로딩되기 전까지 렌더링을 중단합니다.
  • swap: 폴백 폰트로 글자를 렌더링하고, 로딩이 완료되면 웹 폰트를 적용합니다.
  • fallback: 100ms 동안 텍스트가 보이지 않다가 폴백 폰트로 렌더링됩니다. 3초 안에 로딩되면 웹 폰트로 전환됩니다.
  • optional: 100ms 동안 텍스트가 보이지 않다가 폴백 폰트로 렌더링됩니다. 0.1초 내로 폰트가 다운로드되지 않으면 폴백 폰트를 사용합니다.
  1. 적절한 이미지 크기 설정
    • width: 100%; height: auto;를 사용하면 높이를 이미지 다운로드 시점까지 알 수 없기 때문에 레이아웃 이동이 크게 발생할 수 있습니다.
    • widthheight를 지정하면 aspect-ratio 속성 덕분에 이미지를 로딩하기 전에도 적절한 이미지 면적을 할당할 수 있습니다.

12.5.6 핵심 웹 지표는 아니지만 성능 확인에 중요한 지표들

  • 최초 바이트까지의 시간 (Time To First Byte, TTFB): 브라우저가 웹페이지의 첫 번째 바이트를 수신하는 데 걸리는 시간입니다. 서버 사이드 렌더링을 하고 있다면, getServerSideProps 함수 실행까지 시간이 오래 걸릴수록 빈 화면이 뜨기 때문에 사용자 경험을 해칠 수 있습니다.
    • 서버 사이드 렌더링 시, API 호출 횟수와 가져오는 정보의 크기를 최소화하여 응답 속도를 빠르게 하고 크기를 줄일 필요가 있습니다.
    • 웹페이지의 국적에 가깝게 서버를 위치시키는 것이 좋습니다.
    • React 서버 사이드 렌더링에서는 renderToNodeStream, renderToStaticNodeStream과 같은 스트리밍 API를 사용하는 것이 좋습니다.
  • 최초 콘텐츠풀 페인트 (First Contentful Paint, FCP): 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될 때까지의 시간입니다.
    • TTFB를 개선합니다. 일단 무엇이든 다운로드가 시작돼야 렌더링할 수 있습니다.
    • 렌더링을 가로막는 리소스를 최소화합니다. JavaScript, CSS 리소스를 최소화하거나 비동기적으로 로드하도록 합니다.
    • Above the Fold에 대한 최적화: 스크롤하지 않아도 보이는 영역에서는 lazy loading을 하지 않고, 스크립트에 의존해 렌더링하지 않도록 합니다.
    • 페이지 리다이렉트를 최소화합니다.
    • DOM 크기를 최소화합니다. HTML 크기가 크다면 그만큼 렌더링하는 데 시간이 오래 걸립니다. DOM이 필요 이상으로 많고 복잡하다면 이를 줄일 방법을 고민합니다.

Above the Fold란?

Above the Fold는 사용자가 페이지를 처음 열었을 때 스크롤 없이 바로 볼 수 있는 화면 영역을 말합니다. 이 영역에 표시되는 콘텐츠는 페이지가 로드된 후 즉시 사용자에게 노출되어야 하므로 성능 최적화가 특히 중요합니다.

12.6 정리

웹페이지를 개발하다 보면 기능 완성이나 버그 해결에 치중하기 때문에 성능에 대해 깊게 고민할 시간이 부족할 수 있습니다. 하지만 개발자라면 성능 또한 완성도만큼 중요하게 고려해야 합니다. 아무리 좋은 콘텐츠가 제공되더라도 사용자의 환경은 생각보다 열악할 수 있으며, 성능이 나쁘면 사용자는 금방 이탈할 수 있습니다.

성능 개선은 작은 차이로도 큰 효과를 볼 수 있지만, 그만큼 어렵고 세심한 작업이 필요합니다.

반응형