나는 웹 성능 지표를 잘 알고 있었나?
요즘 Next.js 의 사용으로 렌더링 방식에 따른 FCP, LCP 등에 영향을 어떻게 미치는지와 웹 성능과 관련된 개념들을 더 깊게 알 필요가 생긴 것 같다. 때문에 웹 성능 최적화 지표에 대한 개념을 다시 정리해보고자 이렇게 글로 작성한다.
FCP - First Contentful Paint
FCP 매트릭은 First Contentful Paint의 약자로 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될 때까지의 시간을 측정한다. 이 메트릭에서 콘텐츠란 텍스트, 이미지(배경 이미지 포함),
<canvas>
요소 및 svg
요소를 뜻한다.
참고: https://w3c.github.io/paint-timing/#first-contentful-paint
위의 사진을 보면 두 번째 화면에서 최초 텍스트와 이미지 요소 등이 나온것을 볼 수 있다. FCP는 이 프레임에서 발생하게 된다. FCP는 콘텐츠의 "일부" 가 화면에 렌더링될 때까지의 시간을 측정하기 때문에 이따 설명할, 페이지의 주요 콘텐츠 로딩이 완료된 시점을 측정하는 것을 목표로 하는Large Contentful Paint(최대 콘텐츠풀 페인트, LCP) 와는 구분된다.
우수한 사용자 경험을 제공하려면 사이트의 FCP는 1.8초 이하여야 좋다. FCP가 빠르면 사용자는 어떤 일이 일어나고 있다는 것을 빠르게 인지할 수 있고 안심시킬 수 있어 중요하다.
아래와 같이 FCP를 개선하는 방법들이 있다.
- 렌더링 차단 리소스 제거
- CSS 축소
- 사용하지 않는 CSS 제거
- 필요한 원본에 사전 연결
- 서버 응답 시간 단축(TTFB)
- 여러 페이지 리디렉션 방지
- 핵심 요청 사전 로드
- etc...
그런데 일반적으로 FCP의 속도를 느리게 하는 원인은 스크립트 및 스타일시트 때문이 다수라고 한다.
TTI - Time to Interactive
TTI는 Time to Interactive 의 약자로 페이지가 로드되기 시작한 시점부터 주요 하위 리소스가 로드되고 사용자 입력에 신속하게 안정적으로 응답할 수 있는 시점까지의 시간을 측정한다. 쉽게말해 페이지가 완전히 상호 작용 하는 데 걸리는 시간이다.
TTI를 측정하는 방법은 다음과 같다.
- FCP시점 이후부터 최소 5초 정도의 조용한 기간을 탐색하는데 조용한 기간이란, 긴 작업이 없고 전송 중 네트워크 GET 요청이 2개 미만인 기간을 뜻한다.
- 긴 작업이 발견되지 않으면 조용한 기간 이전의 마지막 긴 작업을 역방향으로 검색하며 FCP에서 종료한다.
- TTI는 조용한 기간이 발생하기 이전 마지막 긴 작업의 종료 시간이거나, 긴 작업이 발견되지 않았을 경우에는 FCP와 동일한 값이다.
다이어그램으로 표현하면 다음과 같다.
TTI는 위 같은 특성의 지표이기 때문에 페이지가 어떠한 렌더링 방식을 취했는지와 연관이 깊다.
TTI가 너무 길면 사용자가 응답속도가 너무 느리다며 페이지에 문제가 있다고 인식하고 떠날수도 있다. 또한, 페이지가 아직 상호작용 중이라고 로딩화면과 같이 가시적으로 표현하여 분명히 알려줘야 한다. 그래서 FCP와 TTI 사이에서 적절한 차이를 최소화 해야한다.
우수한 사용자 경험을 제공하기 위해 사이트는 평균 모바일 디바이스에서 테스트할 때 상호 작용까지의 시간이 5초 미만이 될 수 있도록 해야 한다.
TTI를 개선하는 방법에는 다음과 같은 방법들이 있다.
- Javascript 축소
- 필요한 원본에 사전 연결
- 핵심 요청 사전 로드
- 타사 코드의 영향 줄이기
- 크리티컬 요청 깊이 최소화
- JavaScript 실행 시간 단축
- 메인 스레드 작업 최소화
- 요청 수를 낮게 유지하고 전송 크기를 작게 유지
그런데 이러한 지표의 문제는 사용자의 요구에 유연하지지 않다는 것이다. 예를 들어 사용자가 아직 메시지를 보낼 수 없는 경우에도 메시지가 빠르게 렌더링되는 경우가 있을 수 있다. 또한, 게임의 경우에 모든 그래픽 리소스가 로드될 때까지 기다리고 있는데 사운드트랙이 준비되어 완료되는 것이 있을수도 있다.
Speed Index
Speed Index 속도 지수는 페이지 로드 중에 콘텐츠가 시각적으로 표시되는 속도를 측정한다.
Speed Index를 향상시키기 위해서는 다음과 같은 방법이 있다.
- 메인 스레드 작업 최소화
- JavaScript 실행 시간 단축
- 웹폰트 로드 중에 텍스트가 계속 표시
TBT - Total Blocking Time
TBT는 페이지가 마우스 클릭, 화면 탭 또는 키보드 누르기와 같은 사용자 입력에 응답하지 못하도록 차단된 총 시간을 측정한다. 차단 시간은 FCP와 TTI 사이에서 발생하는 각각의 긴 작업에 대한 차단 시간을 합한 것이다.
작업이 길어지는 경우(예: 50ms 이상) 사용자는 지연을 알아차리고 페이지가 느리거나 버벅거리는 것으로 인지하게 된다.
다음 다이어그램을 예시를 살펴보자.
위의 타임라인에는 5개의 작업이 있으며 그 중 3개는 지속 시간이 50ms를 초과하기 때문에 긴 작업으로 간주된다. 다음 다이어그램은 각각의 긴 작업에 대한 차단 시간을 보여준다.
따라서 메인 스레드에서 작업을 실행하는 데 소요된 총 시간은 560ms이지만 차단 시간으로 간주되는 것은 345ms뿐이다.
TBT는 사용자가 페이지에서 안정적으로 상호 작용 환경이 되기 전, 상호 작용이 불가능했을 때의 심각성을 표현한 지표이기 때문에 TTI와 함께 보기에 가장 좋은 지표이다.
우수한 사용자 경험을 제공하기 위해 사이트는 평균 모바일 하드웨어에서 테스트할 때 총 차당 시간이 300ms 미만이 될 수 있도록 해야한다.
TBT를 개선하는 방법에는 다음이 있다.
- 타사 코드의 영향 줄이기
- Javascript 실행 시간 단축
- 메인 스레드 작업 최소화
- 요청 수를 낮게 유지하고 전송 크기를 작게 유지
LCP - Largest Contentful Paint
LCP는 Largest Contentful Paint의 약자로 페이지가 처음으로 로드를 시작한 시점을 기준으로 뷰포트 내에 있는 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간을 나타낸다.
우수한 사용자 경험을 제공하려면 사이트의 LCP가 2.5초 이하여야 한다.
LCP는 측정할 때 다음 요소들을 고려한다.
<img>
<svg>
내부의<image>
<video>
- url() 함수를 통해 로드된 배경 이미지가 있는 요소
- 텍스트 노드 또는 기타 인라인 수준 텍스트 하위 요소를 포함하는 블록 수준 요소
LCP에 대해 리포트 된 요소의 크기는 일반적으로 뷰포트 내에서 사용자에게 표시되는 크기이기 때문에 요소가 뷰포트 외부로 확장되거나, 요소가 잘리거나, 보이지 않는, 넘치게 됐을 경우 경우 해당 부분은 요소 크기에 포함되지 않다.
기본 크기에서 크기가 조정된 이미지 요소의 경우 가시적 크기 또는 기본 크기 중 더 작은 것으로 측정된다. 예를 들어 기본 크기보다 훨씬 작게 축소된 이미지는 표시된 만큼의 크기가, 더 크게 늘어나거나 확장된 경우에는 기본 크기만 측정된다.
텍스트 요소의 경우 텍스트 노드의 크기만 고려된다.
모든 요소에 대해 CSS를 통해 적용된 여백, 안쪽 여백, 테두리는 고려되지 않는다.
LCP가 측정되는 예를 확인해보자.
첫 번째 예시에서는 Instagram 로고가 비교적 일찍 로드되어 다른 콘텐츠가 표시된 이후에도 최대 크기의 요소로 남아있다. 따라서 LCP는 세번째 화면에서 측정된다.
Instagram 의 예시에서 타임라인의 첫 번째, 카메라 로고에는 녹색 상자가 없음을 알 수 있다. 이는 <svg>
요소이고 <svg>
요소자체는 현재 LCP 후보로 간주되지 않기 때문이다. 그래서 첫 번째 LCP 후보는 두 번째 프레임의 텍스트이다.
Google 검색 결과 페이지 예에서는 최대 요소는 이미지 또는 로고 로드가 완료되기 전에 표시되는 텍스트 단락이다. 개별 이미지, 다른 텍스트 단락들이 모두 이 단락보다 작기 때문에 처음 일찍 로드되었던 텍스트 단락이 로드 프로세스 전체에서 최대 요소로 유지된다. 따라서 LCP는 두번째 화면에서 측정된다.
LCP를 개선할 수 있는 방법에는 아래 4가지가 있다.
- 느린 서버 응답 시간
- 렌더링 차단 JavaScript 및 CSS
- 리소스 로드 시간
- 클라이언트 측 렌더링
CLS - Cumulative Layout Shift
웹 페이지를 사용하다 보면 아무런 경고 없이 텍스트가 갑자기 움직인다던가, 스크롤이 된다던가, 다른 요소들이 렌더링 되면서 UI의 변형이 생기는 경험을 할 때가 있다. 그러다 갑작스레 다른 링크로 연결되는 버튼을 클릭하기도 한다.
다음 예시와 같은 상황을 맞이할 경우 굉장히 짜증이 날 수 있다.
페이지 콘텐츠의 예기치 않은 이동은 일반적으로 리소스가 비동기식으로 로드되거나 DOM 요소가 기존 콘텐츠 위의 페이지에 동적으로 추가되기 때문에 발생한다. 이것이 더욱 큰 문제가 되는 이유는 개발 중인 사이트가 기능하는 방식이 종종 사용자가 경험하는 방식과 상당히 다르다는 것이다. 대부분 개인화된 콘텐츠나 타사 콘텐츠는 프로덕션 환경에서는 개발 환경과 동일하게 작동하지 않고, 테스트 이미지는 이미 개발자의 브라우저 캐시에 존재하며, 로컬에서 실행되는 API 호출이 너무 빨라 지연이 눈에 띄지 않는 경우가 많기 때문이다.
CLS는 Cumulative Layout Shift의 약자로 실제 사용자에게 이러한 일이 발생하는 빈도를 측정하여 이 문제를 해결하는 데 도움을 주는 지표이다. 우수한 사용자 경험을 제공하려면 사이트의 CLS 점수가 0.1 이하여야 한다.
여기가지 웹 성능 최적화를 위한 관련 대표 지표들을 알아보았다. 프론트엔드 개발자로써 단순히 화면 구현 말고도, 성능적으로 더 나은 웹 페이지 환경을 제공해줄 수 있도록 항상 고민하는 것도 중요한 역량이라고 생각한다.
이러한 개념들을 숙지하여 프론트엔드 개발할 때 참고하여 실무에 녹여내보면 좋을 것 같다.
참고자료
1. https://web.dev/
2. https://www.debugbear.com/
3. https://wicg.github.io/