사용자가 웹사이트에 접속하였을때 웹사이트의 성능을 나타내기 위한 핵심 웹지표란 개념이 있다.
핵심 웹지표?
구글에서 만든 지표로 웹사이트에서 뛰어난 사용자 경험을 제공하는데 필수적인 지표를 일컫는 용어이다.
1. LCP (최대 콘텐츠풀 페인트)
2. FOD (최초 입력 지연)
3. CLS (누적 레이아웃 이동)
LCP?
최대 콘텐츠풀 페인트란
페이지가 처음으로 로드를 시작한 시점부터 뷰포트 내부에서
가장 큰 이미지나 텍스트를 렌더링하는데 걸리는시간
을 의미한다.
큰 이미지와 텍스트는 아래와 같이 정의되어 있다.
<img>
<svg> 내부 <image>
poster 속성을 사용하는 <viedo>
url()을 통해 불러온 배경이미지가 있는 요소
텍스트와 같이 인라인 텍스트 요소를 포함하고 있는 블록 레벨 요소 (<p>,<div>)
즉, 콘텐츠풀 페인트란 사용자의 기기가 노출하는 뷰포트 내부에서 가장 큰 영역을 차지하는 요소가 렌더링 되는데 걸리는 시간을 측정하는 지표이다.
실제 크기가 커도 뷰포트 내부에 있지 않다면 고려되지 않는다. 오직 뷰포트 영역 내부의 렌더링만 생각한다!!
LCP를 어떻게 측정할 수 있을까? 우선적으로 생각할 수 있는것은 DOMContentLoaded 이벤트가 호출되는 시간이다. 해당 이벤트는 HTML문서를 완전히 불러오고 파싱했을 때 발생하는 이벤트이다. 하지만 해당 이벤트는 스타일시트,이미지,하위 프레임의 로딩을 기다리지 않기에 완벽하게 LCP를 측정할수는 없다.
잘 생각해보면 사용자가 페이지로딩을 체감하는 경우 굳이 모든 페이지가 로딩될 필요는 없다. 페이지가 전체적으로 렌더링이 되지 않아도 사용자에게 노출된 부분만 로딩되어 있다면 사용자 입장에서는 페이지 로딩이 되었다고 느끼기 때문이다.
이러한 지표를 정확하게 체크하기 위해 LCP가 존재한다.
평가
LCP에서는 2.5초 이내로 응답이 오는것을 좋은 점수로 생각하며 4초 이상이걸리면 나쁘게 평가한다.
개선 방안
1. 텍스트의 중요성
아무리 이미지를 최적화해도 이미지는 텍스트를 잡을 수 없다. 가능한 영역을 텍스트로 채우는 것이 성능에 영향을 미치게 된다.
2. 이미지를 불러오는 방법
아래 4가지 이미지 중에서 어떠한 방법이 가장 빠를까?
<img src="lcp.jpg"/>
<svg xmlns="http://www.w3.org/1000/svg>
<image href="lcp.jpg"/>
</svg>
<viedo poster="lcp.jpg"></video>
<div style="background-image : url(lcp.jpg)">...</div>
결과부터 말하자면 img태그와 viedo태그를 사용하는것이 더 빠르다.
img 태그는 브라우저의 프리로드 스캐너에 의해 먼저 발견되어 빠르게 요청이 일어난다.
프리로드 스캐너?
HTML을 파싱하는 단계를 차단하지 않고 이미지와 같은 리소스를 먼저 찾아 로딩하는 브라우저의 기능
svg 내부의 img는 모든 리소스를 다 불러온 다음에 이미지를 불러오게 된다. 즉, svg내부의 img는 병렬적인 다운로드가 진행되지 않는다.
video의 poster또한 img와 같은 성능을 나타낸다. 재밌는것은 poster가 없다면 video의 첫 화면을 로딩하여 poster로 삼기 때문에 poster를 넣어주는 것이 성능 향상에 도움이 된다.
background-image는 느리다. css에 있는 리소스는 DOM을 그릴 준비가 될 때까지 리소스 요청을 미루기 때문이다.
3. 애니메이션
당연한 말이지만 많은 애니메이션은 LCP에 나쁜 영향을 준다.
4. useEffect
뷰포인트에 보이는 영역에 useEffect가 있다면 영역이 전개되고 -> 작업을 수행하고 -> 다시 영역을 그리기 때문에 당연히 LCP에 악영향을 준다. 따라서 가능하다면 이 부분은 서버에서 미리 빌드된 채로 오는 것이 좋다. (Next.js 사용시)
5. 직접 호스팅
가능하다면 해당 리소스는 같은 도메인에서 직접 호스팅하는 것이 좋다. 즉, 이미지를 다른 출처에서 가져오는것이 좋지 않다.
FID(최초 입력 지연)
온라인 예매사이트나 수강신청을 할때 순간적으로 몰리는 트래픽때문에 클릭,타이핑 등 아무 작업이 진행되지 않는 경험을 한번쯤은 다들 해보았을 것이다.
웹페이지의 로딩 속도만큼 중요한 것이 웹사이트의 반응 속도이다. 이런 반응성을 측정하는 지표가 바로 FID이다.
사용자가 페이지와 처음 상호 작용할 때부터 해당 상호작용에 대한
응답으로 브라우저가 실제로 이벤트 핸들러 처리를 시작할때까지의 시간
웹 개발자들은 특별한 이유가 있는 경우를 빼고는 사용자 입력에 대한 처리를 고의로 막지 않는다. 즉, 대부분 이베트 핸들러에는 이벤트를 즉시 처리하기 위한 코드를 넣는다는 건데 왜 이벤트의 반응이 느려지게 될까?
대부분의 이유는 입력을 처리해야 하는 브라우저의 메인 스레드가 바쁘기 때문이다. 대규모의 렌더링이 일어나거나 대규모 JS파일을 분석하고 실행다는데 리소스가 할애되고 있기 때문이다. 따라서 메인스레드가 바쁜 경우 싱글스레드인 JS특성 상 이벤트 리스너와 같은 다른 작업을 동시에 실행할 수 없기 때문에 지연이 발생할 수밖에 없다.
요약하자면 이벤트가 발생하는 시점에 메인 스레드가 다른 작업을 처리할 수 있는 여유를 만들어주는 것이 매우 중요하다.
또다른 이유로는 사용자의 입력이 있다. 타이핑,터치,클릭 등 웹사이트에서할 수 있는 입력은 정말 다양하다. 그중에서 스크롤이나 핀치 투 줌은 애니메이션으로 분류해서 측정 대상에서는 제외한다.
RAIL ?
구글이 사용자 경험을 분류하여 정의한 것.
Response : 사용자 입력에 대한 반응속도 (50ms미만 처리)
Animation : 애니메이션의 각 프레임을 10ms미만으로 처리할 것
Idle : 유휴 시간을 그대화하여 페이지가 50ms이내에 사용자 입력에 응답하도록 할 것
Load : 5초 이내에 콘텐츠를 전달하고 인터랙션을 준비할 것
기준 점수
최초 입력 지연의 좋은 점수를 얻기 위해서는 100ms이내로 응답이 와야하며 300ms이내는 보통 그 이후를 나쁨으로 평가한다.
개선방안
FID(최초 입력 지연)을 개선하려면 메인 스레드에 이벤트를 실행할 여유를 줘야 한다.
1. 실행에 오래 걸리는 작업을 분리
메인 스레드를 오래 점유해야 하는 긴 작업은 웹페이지 전반에 악영향을 미친다. 만약 실행에 오래 걸린다면 몇가지 대안을 연구해야 한다.
먼저, 꼭 웹페이지에서 해야하는 작업인지 생각할 필요가 있다. 가급적 서버로 옮겨서 처리하는것이 사용자에게 유리하게 작용할 수 있다. 또한 긴 작업을 웹페이지에서 해야한다면 해당 작업을 여러개로 분리하여 한 작업이 스레드를 오랫동안 점유하지 못하게 만드는 방법을 적용할 수 있다.
2. JS 코드 최소화
현재의 번들링 도구는 잘되어 있기 때문에 최종 프로덕션 코드는 많이 압축되어있다. 그럼에도 불필요한 코드들을 100% 없애는 건 사실상 불가능하다.
이러한 코드들을 급하지 않은 코드로 간주하여 지연로딩 기법이나 사용자가 필요할때만 불러온다던가 하는 방법을 적용시킬 수 있다.
특히, 폴리필의 코드 크기는 생각보다 크다. 따라서 폴리필을 집어넣기 전에 꼭 폴리필이 필요한지, 내가 지원하는 서비스가 구형 브라우저 를 대상으로 할건지 등등을 생각해보는 것이 좋다.
3. 타사 JS코드 실행 지연
웹 서비스를 만들 때 꼭 개발자가 구축한 코드만 들어가란 법은 없다. Google Analytics나 Firebase와 같이 웹페이지의 통계 집계를 위해 3자의 스크립트를 넣는 경우도 분명 많다. 이런 코드는 물론 통계를 구축하는데 중요하지만 해당 코드 실행으로 인해 사용자에게 안좋은 경험을 안겨준다면 주객이 전도되는 상황에 처할 수 있다.
따라서 script의 async나 ㅇefer옵션을 활용하여 스크립트를 늦게 불러오는 방법을 적용할 수 있다.
defer : 해당스크립트를 다른 리소스와 함께 병렬로 다운로드하며 실행은 페이지가 완전히 로딩된 이후 실행한다.
async : 리소스의 다운로드가 완료되면 다른 리소스의 다운로드가 완료되는 것을 기다리지 않고 실행한다.즉 async리소스의 실행순서는 다운로드 완료 순서가 된다.
X : 둘다 적용되어있지 않다면 script를 만나는순가 다운로드하고 곧바로 코드를 실행한다.
누적 레이아웃 이동(CLS)
웹사이트에서 로딩이 끝난줄 알고 클릭하려고 했는데 다른 요소가 로딩되거나 출력되며 클릭하려는 요소가 없어지는 불쾌한 경험을 다들 해보았을 것이다. 이처럼 페이지의 생명주기 동안 발생하는 모든 예기치 않은 이동에 대한 지표를 계산해주는 것이 누적 레이아웃 이동이다.
과거의 웹사이트는 제한적인 트래픽, 리소스로 인해 제공하는 정보가 한정적이었지만 현재의 웹사이트는 그렇지 않다.
useEffect를 생각해보자. useEffect는 렌더링이 끝난 이후 콜백함수를 실행시키는 훅으로 useEffect 훅이 많을수록 클라이언트에서 처리해야 하는 작업이 많아지며 UI를 변경하는 작업이 있다면 누적 레이아웃 이동또한 나빠질 것이다.
해당 수치는 LCP처럼 사용자의 가시적인 콘텐츠에 영향을 미치는 지표로 뷰포트 내부요소에 대해서만 평가하며 당연하게 사용자 액션으로 인한 레이아웃 이동은 점수에 포함되지 않는다.
영향분율 : 레이아웃 이동이 발생한 요소의 전체 높이와 뷰포트 높이의 비율
거리분율 : 레이아웃 이동이 발생한 요소가 뷰포트 대비 얼마나 이동했는지를 의미
CLS는 이 두 점수를 곱하여 만든다.
개선 방안
1. 삽입이 예상되는 요소를 위한 공간 생성
CLS는 뒤늦게 삽입되는 클라이언트의 동적인 요소로 발생한다. 따라서 useEffect내부에서 UI에 영향을 미치는 작업을 최소화 하는것이 좋다. useEffect말고 useLayoutEffect훅을 활용하여 CLS점수를 낮출 수 있겠지만 이는 오히려 로딩이 오래보이는 역효과를 낳을 수 있으니 주의하여 사용해야 한다.
서버 사이드 렌더링을 활용하여 클라이언트에 HTML을 깔끔하게 제공한다면 클라이언트는 해당 부분을 생각할 필요 없기에 CLS가 개선될 것이다.
2. 폰트 로딩 최적화
누적 레이아웃 이동과 폰트이동에는 무슨 상관관계가 있을까? 폰트로 생기는 문제는 크게 아래 두가지가 있다.
FOUT(flash of unstyled text) HTML에서 지정한 폰트대신 기본폰트로 지정되어 있다가 뒤늦게 폰트가 바뀜
FOIT*flash of invisible text)HTML에서 지정한 폰트와 기본폰트가 둘다 없다가 폰트가 로딩되며 뒤늦게 폰트가 페이지에 렌더링됨
폰트는 각각 고유의 높이,너비를 가지고 있기에 레이아웃 이동이 일어날 수 있다. 따라서 사용자기기의 기본 폰트 외 작업을 하는 경우 아래 주의점을 생각하면 좋다.
- <link>의 preload는 페이지에서 즉시 필요로 하는 리소스를 명시하는 기능으로 리소스가 더 빠르게 준비된다.
- font-family optional의 옵션을 활용한다.
- auto : 브라우저가 폰트를 불러오는걸 결정
- block : 폰트로딩전까지 렌더링을 중단
- swap : FOUT방식
- fallback : 100ms간 텍스트가 보이지 않고 폴백폰트로 렌더링
- optional : fallback과 유사하지만 100ms이내로 폰트가 다운로드되어 있다면 폴백폰트를 사용하며 일정기간 폰트를 다운로드하지 못하면 연결을 취소
3. 적절한 이미지 설정
아래 코드를 활용해서 반응형 웹사이트를 만드는 경우 CLS가 커지는 경우를 낳는다. height가 처음에는 사용자 기기의 크기를 모르기 때문에 우선 크게 잡아둔 후 작게 만들기 때문이다.
img {
width : 100%;
height : auto;
}
따라서 width,heigth를 지정해 두는것이 CLS를 낮추는데 도움이 될 수 있다.
'FrontEnd > Deep Dive' 카테고리의 다른 글
[React] Deep Dive 모던 리액트(32) 웹페이지 성능측정 (0) | 2024.03.03 |
---|---|
[React] Deep Dive 모던 리액트(30) Next.js 13 & 리액트 18 (1) | 2024.02.18 |
[React] Deep Dive 모던 리액트(29) Next.js 서버 컴포넌트 (0) | 2024.02.17 |
[React] Deep Dive 모던 리액트(28) Next.js13 라우팅 (1) | 2024.02.16 |
[React] Deep Dive 모던 리액트(27) 리액트 18 변경점 (0) | 2024.02.12 |