준영
도은
12.1 웹사이트와 성능
- 웹사이트에 접속했을 때 공통적으로 기대하는 사항
- 웹사이트를 방문한 목적을 손쉽게 달성 가능
- 빨리
- 보안
- 위 3가지만 달성할 수 있다면, 내부적으로 어떤 코드로 이루어졌는지는 고객들에게 전혀 중요X
- 성능은 다음과 같은 요소에 영향을 미친다
- 1초 내로 로딩되느 사이트는 5초 내로 로딩되는 사이트보다 전자상거래 전환율이 2.5배 더 높다
- 0~5초의 범위에서, 1초 로딩이 늦어질수록 전환율을 4.42%씩 떨어진다. 즉 5초 이상 느려지면 전환율을 20% 가까이 떨어진다
- 페이지 로드 시간이 0~2초 사이인 페이지에서 가장 높은 전환율을 달성할 수 있다
- 그리고 사용자는 이러한 성능에 매우 민감한 것으로 밝혀졌다
- 소비자의 70%는 페이지 속도가 온라인 커머스 사이트를 방문하는 데 영향을 미친다고
- 절반 가까운 사람이 더욱 빠르게 로딩할 수 있다면 애니메이션과 동영상이 필요 없다고 밝혔다
12.2 핵심 웹 지표란?
- 구글에서 만든 지표
- 웹사이트에서 뛰어난 사용자 경험을 제공하는 데 필수적인 지표
- 구글에서 핵심 웹 지표로 꼽는 지표는
- 최대 콘텐츠풀 페인트(LCP: Largest Contentful Paint)
- 최초 입력 지연(FID: First Input Delay)
- 누적 레이아웃 이동(CLS: Cumulative Layout Shift)
- 그리고 다음 두 지표는 핵심까지는 아니지만, 특정 문제를 진단하는 데 사용될 수 있다고 언급
- 최초 바이트까지의 시간(TTFB: Time To First Byte)
- 최초 콘텐츠풀 시간(FCP: First Contetnful Patnt)
12.3 최대 콘텐츠풀 페인트(LCP)
12.3.1 정의
💡 페이지가 처음으로 로드를 시작한 시점부터 뷰포트 내부에서 가장 큰 이미지 또는 텍스트를 렌더링하는 데 걸리는 시간
- 뷰포트는 사용자에게 현재 노출되는 화면을 의미
- 뷰포트 내부에서 큰 이미지와 텍스트는 다음과 같이 정의
<img>
<svg>
내부의<image>
- url()을 통해 불러온 배경 이미지가 있는 요소
- 텍스트와 같이 인라인 텍스트 요소를 포함하고 있는 블록 레벨 요소
- 사용자의 기기가 노출하는 뷰포트 내부에서 가장 큰 영역을 차지하는 요소가 렌더링되는 데 얼마나 걸리는지를 측정하는 지표
- 실제 크기가 크다고 하더라도 뷰포트 영역 밖에 넘치는 요소가 있다면, 해당 영역의 크기는 고려 X
- 아무리 콘텐츠 높이가 길어도, 최대 콘텐츠풀 페인트에 영향을 미치는 부분은 오직 뷰포트 영역뿐
12.3.2 의미
- 웹페이지가 로딩이 완료되어 사용자에게 노출되기까지 걸리는 시간을 측정할 때 기준
- 사용자가 페이지가 어느정도 로딩되었다고 인식하는 시점
- 사용자에게 페이지의 정보를 화면에 전달하는 속도를 객관적으로 판단하기 위한 지표로 만들어진 것
12.3.5 개선 방안
텍스트는 언제나 옳다
- 최대 콘텐츠풀 페인트 지표에서 좋은 점수를 얻는 가장 확실한 방법
- 뷰포트 최대 영역, 즉, 최대 콘텐츠풀 페인트 예상 영역에 이미지가 아닌 문자열을 넣는 것
- 아무리 최적화를 하더라도 다운로드가 필요한 이미지보다 텍스트 노출이 훨씬 빠르다
이미지는 어떻게 불러올 것인가?
개발자가 선택할 수 있는 이미지를 노출하는 방법에는 다음과 같은 여러 가지가 있는데
// 1) img
<img src="lcp.jpg" />
// 2) svg
<svg xmlns="http://www.w3.org/1000/svg">
<image href="lcp.jpg"/>
</svg>
// 3) (비디오의 경우) video.poster
<video poster="lcp.jpg"></video>
// 4) background-image: url()
<div style="background-image: url(lcp.jpg)">...</div>
- 1번과 3번 예제 코드가 더 빠르게 완성되는 것을 확인할 수 있다
<img>
- 이미지는 브라우저의 프리로드 스캐너에 의해 먼저 발견되어 빠르게 요청
- 프리로드 스캐너: HTML을 파싱하는 단계에서 차단하지 않고 이미지와 같이 빠르게 미리 로딩하면 좋은 리소스를 찾아 로딩하는 브라우저의 기능
- 내부의 리소스는 이처럼 프리로드 스캐너가 병렬적으로 리소스를 다운로드하므로 LCP 요소를 불러오기에 적절한 방법
<picture>
도 마찬가지- 즉, 자바스크립트 리소스의 다운로드가 완료되지 않았음에도 프리로드 스캐너 덕분에 미리 발견되어 병렬 다운로드
<svg>
내부의<img>
- 모든 리소스를 다 불러운 이후에 이미지를 불러온다
- 즉, 자바스크립트 리소스가 다운로그가 완료된 후에야 이미지를 다운로드
- 프리로드 스캐너에 의해 발견 X
- LCP 점수에도 악영향
<video>
의 poster- poster는 사용자가 video 요소를 재생하거나 탐색하기 전까지 노출되는 요소
- 프리로드 스캐너에 의해 조기 발견
- poster가 없는 video의 경우, video를 실제로 로딩해 첫 번째 프레임을 해당 poster 리소스로 대체할 예정
- 따라서, video가 LCP에 영향을 맏을 것 같다면 poster를 반드시 넣어주는 것이 좋다.
- background-image
- css에 있는 리소스는 항상 느리다
- 리소스는 브라우저가 해당 리소스를 필요로 하는 DOM을 그릴 준비가 될 때까지 리소스 요청을 뒤로 미루기 때문
그 밖에 조심해야 할 사항
- 이미지 무손실 압축
- 웹으로 서비스할 이미지는 가능한 한 무손실 형식으로 압축해 최소한의 용량으로 서비스하는 것이 좋다
- loading=lazy 주의
- 리로스를 중요하지 않으믕로 표시하고 필요할 때만 로드하는 전략
- LCP의 이미지는 중요하지 않은 리소스로 분류해서는 안 된다
- 그저 로딩 속도만 늦출 뿐 지표 점수에는 도움 X
- 상대적으로 중요하지 않은 이미지에서는 사용해도 좋지만, LCP 이미지에는 사용하지 않는 것이 좋다
- fadein과 같은 각종 애니메이션
- 당연하게도 이미지가 그냥 뜨는 것보다 fadeIn ease 10s와 같이 처리한다면 LCP도 그만큼 늦어진다
- 클라이언트에서 빌드하지 말 것
- 서버에서 빌드해온 HTML을 프리로드 스캐너가 바로 읽어서 LCP로 빠르게 가져가는 것이 최적의 시나리오
- 만약 다음과 가틍ㄴ 코드가 있다면?
useEffect(() => { (async () => { const result = await fetch('https://example.com/data'); if (result.ok) { setShow(true); // LCP 영역을 노출함 } })(); }, []);
- 위와 같이 된다면, HTML을 다운로드한 직후가 아닌 리액트 코드를 파싱하고 읽어서 API 요청을 보내고, 응답을 받은 만큼 늦어지게 된다
12.4 최초 입력 지연(FID)
12.4.1 정의
- 순간적으로 몰린 엄청난 트래픽 때문에 웹사이트가 클릭이나 타이핑도 되지 않은 경험이 있을 것이다.
- 제아무리 페이지가 빨리 로딩된다 하더라도 사용자가 클릭을 비롯한 상호작용을 할 수 없다면, 느리다고 생각할 것 → 부정적 경험
- 웹페이지의 로딩 속도만큼 중요한 것이 웹사이트의 반응 속도
💡 웹사이트의 반응성을 측정하는 지표
사용자가 페이지와 처음 상호 작용을 할 때부터 해당 상호 작용에 대한 응답으로
브라우저가 실제로 이벤트 핸들러 처리를 시작하기까의 시간을 측정
12.4.2 의미
🤔 웹사이트 내부의 이벤트가 반응이 늦어지는 이유는?
- 브라우저의 메인 스레드가 바빠서
- 대규모 렌더링이 일어나고 있거나
- 대규모 자바스크립트 파일을 분석하고 실행하는 등 다른 작업을 처리하는 데 리소스를 할애하고 있기 때문
- 자바스크립트 실행 환경이 싱글 스레드이기 때문에
- 이벤트 리스너와 같은 다른 작업을 실행할 수 없어 지연이 발생하는 것
12.4.5 개선 방안
- 최초 입력 지연에 가장 큰 영향을 미치는 메인 스레드에 이벤트를 실행할 여유를 줘야 한다
실행에 오래 걸리는 긴 작업을 분리
- 꼭 웹페이지에서 해야 하는 작업인가
- 개발자의 최신 개발 기기에서도 오랜 시간이 소요되는 작업이라면
- 실제 사용자가 이용하는 경우에는 더욱 오래 걸릴 것
- 저사양 기기에서도 오래 걸리는 작업이라면 고려
- 꼭 클라이언트에서 해야 하는 작업인지를 고민해보고, 그게 아니라면 서버로 옮겨서 처리하는 것이 좋다
- 서버에서 처리하면 상대적으로 빠르고 브라우저의 메인 스레드를 오래 점유하지 않게 할 수 있다
- 긴 작업을 여러 개로 분리하기
- 만약 꼭 웹페이지에서 처리해야 하는 작업이라면
- 해당 작업을 여러 개로 분리하는 것이 좋다.
- 분리하고, 최초 로딩에 필요하지 않은 내용을 나중에 불러오는 것도 포함
자바스크립트 코드 최소화
- 번들러가 코드를 만들어주는 과정에서 어느 정도 필요없는 코드를 제거할지라도
- 경우에 따라 웹페이지를 불러오는 데 사용되지 않는 필요없는 코드가 존재할 수 있다.
- 개발자 도구 탭에서 도구 더보기 > 커버리지 기록을 해보면
- 현재까지 웹페이지에서 사용되지 않은 코드가 얼마나 있는지 확인 가능
12.5 누적 레이아웃 이동(CLS)
12.5.1 정의
- 갑자기 화면에서 레이아웃이 이동하게 되면, 예기치 못한 작동을 야기해 사용자가 우너래 하려던 동작을 방해할 수도
💡 페이지의 생명주기 동안 발생하는 모든 예기치 않은 이동에 대한 지표를 계산
12.5.2 의미
- 누적 레이아웃 이동은 사용자의 가시적인 콘텐츠에 영향을 미쳐야 하기 때문에
- 뷰표트 내부의 요소에 대해서만 측정
- 사용자 액션으로 발생한 레이아웃 이동은 점수에 포함 X
12.5.5 개선 방안
삽입이 예상되는 요소를 위한 추가적인 공간 확보
useEffect
사용이 불가피하다면,useLayoutEffect
를 사용하는 것도 검토해보자- DOM이 업데이트된 직후, 브라우저가 화면을 그리기 전에 동기적으로 실행
- 스켈레톤 UI처럼, 미리 무언가가 동적으로 뜰 것으로 예상되는 공간을 미리 확보해 두는 것도 좋은 방법
- 레이아웃 이동을 막으면서 클라이언트 시점에 정해지는 콘텐츠를 안정적으로 보여줄 수 있다
- 가장 좋은 방법은 서버 사이드 렌더링
- 서버에서 이러한 동적인 요소의 유무를 사전에 판단해서 클라이언트에 HTML을 미리 제공한다면
- 클라이언트는 이러한 고민을 할 필요 X
폰트 로딩 최적화
- 폰트 또한 레아아웃 이동을 시키는 원인 중 하나
- FOUT(flash of unstyled text)
- HTML 문서에서 지정한 폰트가 보이지 않고
- 대체 기본 폰트로 보이고 있다가 뒤늣게 폰트가 적용되는 현상
- FOIT(flash of invisible text)
- HTML 문서에서 지정한 폰트가 보이지 않고
- 기본 폰트도 없어서 텍스트가 없는 채로 있다가
- 뒤늦게 폰트가 로딩되면서 페이지에 렌더링되는 현상
- 폰트는 각각 고유의 높이와 너비 보유
- 최대한 중요한 폰트의 다운로드를 우선순위에 밀어넣고
- 이 우선순위를 활용했음에도 빠르게 로딩하는 데 실패했다면
- 다음을 기약하고 기본 폰트를 노출하는 것
적절한 이미지 크기 설정
-
반응형 웹사이트에서 다음과 같이 이미지를 기기에 의존하도록 스타일을 적용한다
img { width: 100%; height: auto; }
-
위 경우는 누적 레이아웃 이동을 커지는 결과를 낳는다
-
앞서 설정한 CSS 때문에
- 이미지가 완전히 다운로드되기 전까지는 알 수 없기 때문에
- 이미지의 높이를 높게 잡아 뒀다가
- 이미지가 완전히 로딩 완ㄹ된 이후에 기기의 높이만큼 계산해
- 마침내 이미지 크기만큼 자리를 잡을 수 있게 된 것
-
width
와height
를 지정- 지정하는 것이 가장 좋은 방법
-
srcset
속성을 사용하여 브라우저가 상황에 맞게 이미지를 사용할 수 있게 준비<img srcset="image-1000.jpg 1000w image-2000.jpg 2000w, image-3000.jpg 3000w"/>
12.5.6 핵심 웹 지표는 아니지만 성능 확인에 중요한 지표들
최초 바이트까지의 시간(Time to First Byte, TTFB)
💡 브라우저가 웹 페이지의 첫 번째 바이트를 수신하는 데 걸리는 시간
→ 최초의 응답이 오는 바이트까지가 얼마나 걸리는지를 측정하는 지표
- 특히, 서버 사이드 렌더링을 하고 있다면, 주의 깊게 봐야 할 지표
- SSR은 최초 페이지를 만들기 위해 서버에서 어느 정도 작업이 수행
- 첫 번재 HTML을 만들기 위해 해야 하는 작업이 많거나 느릴수록 시간이 길어지게 된다
- 이는 사용자가 페이지를 요청했을 때 빈 화면이 뜨는 것을 의미
최초 콘텐츠풀 페인트(First Contentful Paint, FCP)
💡 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될 때까지의 시간을 측정
→ 웹사이트에 접속한 순간부터 페이지에 뭐라도 뜨기 시작한 시점까지의 시간
- 최초 바이트까지의 시간(TTFB)을 개선
- 일단 뭐라도 다운로드가 시작되어야 렌더링 가능
- 렌더링을 최대한 빠르게 하기 위해 최초 바이트까지의 시간을 단축해야 한다
- 렌더링을 가로막는 리소스 최적화
- 자바스크립트나 CSS 같은 렌더링을 가로막는 리소스를 최소화
- 렌더링을 방해하는 리소스를 비동기적으로 로드하도록 해야 한다.
- DOM 크기 최소화
- HTML의 크기가 크다면, 즉 DOM이 복잡하고 크다면
- 그만큼 렌더링되는 데 시간이 오래 소요
- DOM이 크고 복잡해지면 브라우저가 이를 파악해 렌더링하는 데 시간이 오래 걸리게 된다.
- 웹페이지의 소스 보기로 확인해 보았을 때, DOM이 필요 이상으로 많고 복잡하다면 이를 줄일 수 있는 방법을 고민