[ 살펴보기 ] Frontend - Core Web Vitals

[ 살펴보기 ] Frontend - Core Web Vitals

·

4 min read

Core Web Vitals이란 Web service를 방문하는 유저에게 보다 나은 사용자 경험을 제공하기 위해 지켜져야 하는 사항을 말하며 구글에서 만든 지표다. 그리고 해당 지표는 Loading, Interactivity, Visual Stability라는 세 가지 측면을 집중적으로 다룬다.

Largest Contentful Paint ( LCP )

사용자에게 보여지는 View port에서 가장 큰 이미지나 텍스트를 렌더하는데 소요되는 시간을 말한다 ( 시각적으로 가장 큰 ). 그리고 LCP로 취급되는 element들은 아래와 같다

  • <img> element

  • <svg> element 안에 있는 <image> element

  • <video> element

  • background-image:url() 형식을 통해 image를 load하는 element

  • 텍스트 node를 포함하고 있는 block level의 element

만약 일반적인 게시판 페이지에 접속한다면 헤더와, 페이지 타이틀, 그리고 게시판의 리스트 정보가 출력될 것이다. 이때 LCP는 페이지가 로딩되는 순서에 따라 지속적으로 변한다. 즉, 현재 load된 element중에 가장 큰 element가 LCP가 되며 그 보다 더 큰 element가 load되면 해당 element가 LCP가 된다.

그리고 LCP는 현재 user에서 보이는 view port를 기준으로 판단된다. 즉 현재 user에게 보이지 않고 스크롤을 내려야 보이는 다른 element들은 LCP 지표 대상이 되지 않는다.

그렇다면 LCP를 판단하는 기준은 어떻게 될까?

위의 이미지에서 볼 수 있듯이 좋은 사용자 경험을 제공하기 위해 좋은 LCP로 여겨지는 시간은 2.5초 미만이다.

그렇기에 보다 나은 LCP 지표를 달성하기 위해 페이지에서 image가 사용되고 있다면 반드시 최적화 과정을 거친 image를 load하여 사용해야 할 것이다. 물론 image없이 text만 사용하는 것이 LCP 지표를 위해선 베스트 옵션이겠지만 실제 서비스를 만든다면 모든 페이지에서 이미지를 제외할 수는 없다.

그리고 React 통해 Frontend를 개발한다면 NextJS와 같은 Meta framework를 통해 application build시 정적 페이지를 미리 만들어 두고 application을 client side에서 모두 렌더하는 것이 아닌 준비된 정적 페이지 HTML를 우선 제공함으로서 LCP지표를 높일 수 있다.

Interactive To Next Paint ( INP )

INP는 기존에 있던 FID ( First Input Delay ) 지표를 대체하며 생긴 Core Web Vital 지표다. INP는 사용자가 web application에서 특정 버튼을 click하거나 input등에 정보를 기입하는 등 상호작용을 할 때 해당 이벤트에 따라 발생하는 결과가 얼마나 빨리 UI에 적용 되는가를 나타내는 지표다. 더 정확히는 application의 main thread가 UI 처리가 필요한 상황에 큰 delay없이 UI feedback 처리를 해주고 있는가를 평가하는 지표다. ( 이러한 속성 때문에 해당 지표의 효용성에 대한 의문 역시 존재하는 듯 보인다 : Google's New Core Web Vital (INP) Explained in 5 Minutes )

그렇기에 해당 지표를 설명하기 위해 event와 event에 대한 결과가 synchronous로 처리된다는 가정으로 설명을 이어가겠다.

예를들어 버튼을 눌러 모달을 띄운다면 버튼 클릭이라는 이벤트로부터 해당 이벤트의 결과인 모달이 화면이 띄워질 때까지 소요된 시간을 평가한다. 즉, 유저가 발생시킨 Interaction에 대한 결과가 얼마나 빨리 UI에 반응이 되는가를 평가하는 것이다.

INP를 평가하는 기준은 다음과 같다

유저 event가 발생한 시점부터 200ms안에 그 결과가 유저화면 반영되면 일반적으로 좋은 수치를 가졌다고 할 수 있고 200ms ~ 500ms 범위라면 보통 수준의 수치를 나타내며 그리고 500ms가 넘어간다면 개선의 필요가 있다고 판단된다.

그렇다면 보통 어떤 경우 사용자 Interaction에 문제가 생길까? 이미 알고 있는 사항이겠지만 Javascirpt는 싱글 스레드 기반 언어이기에 Javascript 실행 환경의 메인 스레드가 다른 일을 처리하느라 바쁘면 사용자 Interaction에 문제가 생길 수 있다.

그리고 이러한 일이 발생하는 가장 흔한 이유 중 하나가 Page render 작업이다. 특히 React와 같은 client-side render를 기반으로 하는 framework로 만들어진 application을 render할 때는 application에 필요한 모든 자바스크립트를 분석하고 실행하는데 리소스를 할애하기에 더욱이 이런 문제에 취약해 질 수 있다.

Cumulative Layout Shift ( CLS )

특정 페이지에 접속하여 로그인 버튼이나 검색 서치바를 이용하려고 마우스를 가져대는 순간 해당 page에 추가적인 element가 load되면서 원래 이용하려고 했던 UI 부분이 아래로 밀려나 원래 의도했던 element가 아닌 다른 element를 눌려버리는 경험을 해본 적 있을 것이다. 만약 새로 load된 element를 클릭했을 때 다른 page로 이동하는 event가 추가되어 있다면 사용자는 의도하지 않게 다른 페이지로 이동해버리기에 해당 page의 사용자 경험은 현저히 떨어진다.

이렇듯 페이지 로딩 과정 혹은 로딩 후에 특정 버튼을 눌렀을 때 사용자가 읽고 있던 콘텐츠가 완전히 아래로 밀려 버리거나 기타 Layout shift가 발생해서 원래 누르려고 했던 자리에 전혀 다른 버튼이 위치하게 되어 원래 의도와는 다른 행위를 하게 되는 실수를 야기할 수 있는 Layout shift 발생 정도를 측정한다.

CLS 점수는 Impact Fraction 점수와 그리고 Distance Fraction 점수를 곱하여 구한다.

Impact Fraction은 현재 viewport 기준으로 새로운 elemen의 개입으로 layout shift가 발생했을 때 이전 frame과 현재 frame을 비교하여 새로 생긴 element로 인해 발생한 layout shit가 viewport에 영향을 미친 정도를 측정한다

Distance Fraction은 현재 viewport 기준으로 새로운 element의 개입으로 발생한 layout shit로 인해 기존의 element가 얼마나 이동하였는지 측정한다

그리고 CLS를 평가하는 기준은 다음과 같다

CLS score가 0.1 이하라면 좋음, 0.1 ~ 0.25 사이라면 보통, 0.25 이상이라면 CLS score에 개선이 필요하다고 평가된다.

특히 Client와 Server가 완전히 분리되어 개발되고 React, Vue, Angular와 같은 CSR을 통해 page를 보여주는 개발 스택이 많은 시대이기에 CLS 지수에 조금 더 신경이 필요한 것으로 보인다.

React를 사용하여 개발한다면 NextJS와 같은 Meta framework를 통해 SSR를 적극 활용할 수도 있지만 UI를 서버에서 미리 render할 수 없는 경우도 존재한다. 그렇기에 반드시 client에서 처리되어야 하는 UI는 useEffect 대신 useLayoutEffect를 사용하는 것도 하나의 방법이 될 수 있을 것이다.

그리고 초기 render에 시간이 소요되는 UI작업은 render가 마무리 되기 전까지 skeleton을 대신 보여주고 render가 완료되면 원래 UI로 대체하는 것도 좋은 CLS를 유지할 수 있는 방법으로 생각된다.

Animation이나 Effect가 전혀 없는 application라면 모를까 실제 application에서 layout shift가 아예 없을 수는 없을 것이라고 생각된다. 때로 유저의 눈을 사로잡는 UI를 위해선 layout shift가 발생하는 event는 필요하기에 사용성과 디자인을 함께 잡기 위해선 UX적인 고려가 많이 필요할 것으로 보인다.