LCP 최적화를 위한 웹사이트 분석
내가 작업하는 웹사이트는 해외에서도 서비스하고 있는데, 화면에 주요 컨텐츠를 보여주기도 전에 이탈하는 해외 유저가 꽤 많다는 것을 알게되었다. 이를 해결하기 위해 첫 화면 로드와 관련된 지표인 LCP를 측정하고 웹사이트 로드 과정을 분석했다. LCP를 개선하기 위해 웹사이트를 어떻게 분석했는지 또 어떤 개선 방안을 도출했는지 기록해보려고 한다.
💡 LCP란?
LCP는 구글에서 정의한 핵심 웹 지표 (Core Web Vital) 중 하나이다. 최대 콘텐츠풀 페인트(Largest Contentful Paint)의 약자로, 페이지가 처음으로 로드를 시작한 시점부터 뷰포트 내부에서 가장 큰 이미지, 또는 텍스트를 렌더링하는 데 걸리는 시간을 나타내는 지표다.
페이지의 메인 콘텐츠가 로드되었을 가능성이 있을 때 페이지 로드 타임라인에 해당 시점을 표시하므로 사용자가 감지하는 로드 속도를 측정할 수 있는 중요한 지표다. LCP가 빠르면 사용자가 해당 페이지를 사용할 수 있다고 인지하는 데 도움이 된다.
LCP에 대한 더 자세한 정보는 아래 페이지에서 확인할 수 있다.
Largest Contentful Paint (LCP) | Articles | web.dev
이 게시물에서는 최대 콘텐츠 렌더링 시간 (LCP) 측정항목을 소개하고 측정 방법을 설명합니다.
web.dev
위 페이지의 내용을 아래 접은글에 간단히 정리해두었다.
LCP 지표 평가 기준
- 좋음: 2.5초 이하
- 개선이 필요: 2.5초에서 4초 사이
- 나쁨: 4초 이상
평가에 사용되는 요소
- <img> 요소
- <svg> 내부의 <image> 요소
- <video> 요소 (포스터 이미지나 첫 프레임이 표시되는 시간)
- URL로 로드된 배경 이미지 (CSS 그라디언트는 제외)
- 텍스트를 포함하는 블록 수준 요소
평가에 제외되는 요소
사용자가 콘텐츠로 인식하지 않는 요소들은 LCP 측정에서 제외된다.
- 투명도 0인 요소
- 전체 뷰포트를 덮는 요소 (배경으로 간주될 수 있음)
- 플레이스홀더 이미지 등 콘텐츠를 반영하지 않는 이미지
LCP 측정 방식
요소가 뷰포트 내에 표시된 크기만을 기준으로 하며, 클립된 부분이나 보이지 않는 오버플로우는 계산에 포함되지 않는다. 텍스트의 경우, 모든 텍스트 노드를 포함할 수 있는 최소한의 직사각형 크기만 계산된다.
LCP 보고 타이밍
페이지가 로드되는 과정에서 가장 큰 콘텐츠가 변경될 수 있기 때문에, 브라우저는 처음 렌더된 후 가장 큰 콘텐츠를 기록한 뒤, 더 큰 요소가 나타나면 이를 갱신한다.
🧐 웹사이트가 그려지는 과정 살펴보기
분석에는 WebPageTest(https://www.webpagetest.org/ )사이트를 이용했다. 해당 사이트에서는 해외 환경에서 접속했을 때의 LCP를 포함한 웹사이트 성능에 대한 많은 정보를 제공한다. WebPageTest의 Filmstrip 기능을 이용해 LCP 요소가 그려지기까지 어떤 과정을 거치는지 확인해보았다. 미국에서 접속했을 때 우리 사이트의 LCP는 무려 12초였다. 😱
Filmstrip 기능을 통해 시간대별로 어떤 리소스를 불러오는지, 특정 시각에 화면에는 어떻게 그려지는지 확인이 가능하다. 또한 리소스 별로 시간이 얼마나 걸리는지, 어떤 것이 렌더링을 막고 있는지도 확인할 수 있었다.
이를 바탕으로 웹사이트에서 배너를 그리기까지 오래 걸리는 이유와 해결방안을 도출해 보았다.
🛠️ 발견한 문제점 및 해결방안
WebPageTest의 분석을 통해 파악한 우리 사이트 메인 페이지의 LCP 요소는 상단에 위치한 배너였다. 그러니까 LCP 개선을 위해서는 유저에게 이 메인 배너를 빠르게 보여주면 된다. 이 배너를 그리기까지 어떤 과정을 거치는지 살펴보고 어떻게 개선할 수 있을지 파악해보려 했다.
현재 작업하고 있는 웹사이트는 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR)이 혼합된 형태이다. 서버에서 jsp로 html문서를 동적으로 생성해 제공하고 있긴 하지만 사용자가 보는 대부분의 UI는 React로, 즉 JS 코드 실행으로 렌더링된다.
배너를 그리기까지의 리소스 로드 단계를 정리해보면 크게 다섯 단계로 나눌 수 있었다.
- html 문서 로드
- html head에 작성되어있는 자바스크립트, css 로드
- 리액트 번들 파일 로드 - 배너 렌더링에 필요한 코드가 포함되어있다.
- 배너 컨텐츠 로드 - 배너에 표시할 내용이 설정된 json 컨텐츠
- 이미지, 폰트 로드
각 단계는 대부분 앞 단계에 의존하므로, 각 단계마다 시간을 단축시키는 것을 우선으로 생각할 수 있었다. 보편적으로 사용하는 CDN을 사용해 사용자와 가까운 서버에서 리소스를 제공하고, 이미지와 폰트의 용량을 줄이는 방식은 이미 적용된 상태였다. 그 외의 개선할 수 있는 부분들을 중점으로 확인했다.
html 문서
모든 리소스의 로드는 html을 로드한 뒤부터 시작된다. html을 로드해야 화면을 그리는 js 스크립트도 다운받기 시작할 수 있다. html 을 동적으로 만들지 않고 정적으로 캐싱해 제공한다면 html 응답시간을 단축할 수 있을 것으로 생각했다. 하지만 메인 페이지의 html을 생성하는 jsp 에 서버 환경에 의존하는 코드가 있어 이 방법은 적용할 수 없었다.
현재의 CSR 방식이 아닌, 서버에서 렌더링된 html을 제공하는 SSR 방식도 좋은 LCP 개선 방법이지만 현재 상황에서 채택할 수 있는 방법은 아니었다. 향후 Next.js를 도입할 계획이 있지만, 이는 아직 먼 미래의 일일 것 같다...😶🌫️
초반에 불러오는 리소스 점검
자바스크립트, CSS의 렌더 블로킹
브라우저가 컨텐츠를 렌더링하려면 HTML 마크업을 파싱해서 DOM 트리로 만들어야 한다. HTML 파서는 외부 스타일시트(<link rel="stylesheet">)나 동기적인 자바스크립트 태그(<script src="main.js">)를 만나면 중단될 것이다. waterfall 부분을 확인했을 때 좌측에 주황색으로 'x' 표시된 부분이 렌더블로킹을 발생시키는 요소들이었다. 내용을 확인해 초기 렌더링에 중요하지 않다면 비동기로 불러오도록해 렌더블로킹을 방지할 수 있다. 오래전부터 사용되던 파일들이라, 비동기로 로드했을 때 어떤 사이드이펙트가 있을지 몰라 충분한 검토가 필요할 것 같다 🥹
그외 지연을 발생시키는 리소스 로드과정 점검
그 외에도 렌더블로킹을 하진 않지만 우선순위 높여서 다운로드하는 리소스들이 많았는데, 생각보다 많은 시간을 소요하고 있어 (특히 폰트) 현재 필요한지 검토하고 제거하려고 한다. 동시에 요청이 가능해도 네트워크 자원은 한정되어있기 때문에 불필요한 리소스는 받아오지 않는 것이 최적화에 도움이 될 것이다.
어떤 스크립트 요청은 초기 요청의 응답이 303으로와서 redirect된 위치에서 최신 버전의 스크립트를 다운받아 오기도 했다. 최종 스크립트 파일을 받아올 때까지 다른 요청은 멈춰있었다.
추측상 해당 스크립트가 과거 메인 페이지의 LCP 요소에 사용돼서 preload로 우선순위를 높여 다운받게 해둔 것 같았다. 이제 LCP 요소는 바뀌었고 우선순위를 낮추면 해당 과정에서 발생하는 지연은 개선 될 것이다. (redirect를 하지 않게 하면 더 좋겠지만, 이는 다른 팀의 협력이 필요해 일단 내 작업선에서 끝낼 수 있는 해결방안부터 적용하기로 했다.)
그런데 렌더블로킹을 발생시키지 않으면서 왜 다른 리소스의 요청이 모두 멈췄을까 궁금해졌다. 렌더블로킹이 발생하지 않았다면 파싱이 계속 진행될 테고, 그 뒤에 작성한 리소스들을 계속 요청할 것 같았다. 일단 간단하게 챗gpt에게 물어보았다. 챗gpt의 답변을 정리하자면 해당 리소스의 우선순위가 높고 네트워크 혼잡 관리를 위해 다른 요청이 대기상태가 됐다고 하는 것 같다. 정확한 내용은 아닐 수 있지만 간단히 알아보고 넘어갔다. 추후에 브라우저의 네트워크 요청을 어떻게 관리하는지 더 알아봐야겠다...
배너에 사용되는 이미지, 폰트의 다운로드 시점 앞당기기
배너에서는 몇가지 폰트 스타일을 적용할 수 있도록 해두었는데, 사용하는 폰트만 동적으로 다운 받도록 코드를 작성해 두었다. 그러다 보니 배너 컨텐츠 설정 json까지 다운 받는 4번 단계를 거쳐야만 폰트 다운로드를 시작할 수 있었다. 이로 인해 배너 텍스트에 폰트가 늦게 적용되었다. 또 배너에 사용되는 이미지도 마찬가지였다. 배너에 사용되는 이미지와 폰트들은 미리 다운로드를 시작하게 해두면 배너 텍스트에 폰트가 좀 더 빨리 적용될 수 있을 것이다. 백엔드에서 배너 컨텐츠 설정 json을 받아와 HTML head에 <link> 태그와 preload를 사용해 리소스를 미리 로드하는 방법을 고려 해봤지만, 백엔드 개발자의 협력이 필요한 부분이어서 추가 검토가 필요하다.
첫 화면 영역 외의 이미지의 다운로드 우선순위 낮추기
현재는 이미지에 레이지 로드를 적용하지 않아 페이지에 있는 여러 이미지 리소스들에 대해 동시에 요청하고 있었다. 동시에 요청은 이루어졌지만, 다운로드 우선순위가 지정되지 않아 다른 이미지가 먼저 다운로드되는 것을 확인할 수 있었다. 첫 화면 영역 외의 이미지는 화면에 보였을 때부터 다운로드 하도록 레이지로드를 적용하고, 배너 첫 번째 이미지에 우선순위를 부여해 먼저 다운로드 될 수 있게 하려고 한다.
마치며
리소스 다운로드와 waterfall에 관련된 원인과 해결방안이 주를 이뤘는데, 배너를 렌더링하는 코드의 성능에 대해서는 아직 제대로 파악하지 못해 내용에 포함하지 못했다. 크롬 개발자도구의 성능 탭도 분석에 활용을 했었고 이를 통해 오래걸리는 작업을 확인하긴 했으나, 이것이 배너를 그리는 작업에 영향을 주었는지를 제대로 파악하지 못했다. 😓 시간이 나면 이부분도 좀 더 살펴보고 보충해야겠다.
이번에 LCP 개선을 위해 웹사이트를 분석하면서 렌더링 되기까지 과정을 세심하게 고려해야 한다는 것을 깨달았다. 또 웹사이트가 오래된 만큼 예전에 개발된 부분에 대해서는 신경을 쓰지 못했는데 이번에 한번 검토하고 정리해야겠다는 생각이 들었다.
이번에 분석한 것을 바탕으로 적용하는 작업을 진행한 후에 얼마나 개선 됐는지도 기록해보려고 한다.
참고 자료
Largest Contentful Paint (LCP) | Articles | web.dev
이 게시물에서는 최대 콘텐츠 렌더링 시간 (LCP) 측정항목을 소개하고 측정 방법을 설명합니다.
web.dev
https://yceffort.kr/2022/06/optimize-LCP
Largest Contentful Paint (LCP) 최적화하기
LCP를 최적화 하기전에, 먼저 그 정의를 살펴보자. 최대 콘텐츠풀 페인트(LCP)는 페이지의 메인 콘텐츠가 로드되었을 가능성이 있을 때 페이지 로드 타임라인에 해당 시점을 표시하므로 사용자가
yceffort.kr
https://ui.toast.com/posts/ko_202012101720
LCP(Largest Contentful Paint) 최적화하기
Largest Contentful Paint(LCP)는 Core Web Vitals의 지표이며 뷰포트에서 가장 큰 콘텐츠 엘리먼트가 나타날 때 측정한다. 페이지의 주요 내용이 화면에 렌더링이 완료되는 시기를 결정하는데 사용된다.
ui.toast.com