Before | After |
|---|
이렇게 Lighthouse Performance가 77점에서 98점으로 개선되었다.
하지만 After의 점수는 최고점수이고, 실제로는 여러 변수에 따라 계속 변동이 있었다. 그러나 대부분의 결과에서 90점대 초반 이상은 나왔다.
또한 모바일 사용은 고려하지 않았기 때문에, 데스크탑으로만 검사를 진행했다.
체감하기에도 개선 전에는 배경 이미지가 위에서부터 느리게 조금씩 나타나서 사용자 경험이 나빴는데, 개선 후 훨씬 빠르게 나타나는 것을 볼 수 있었다.
Before | After |
|---|
Lighthouse Overview 공식 문서에 따라 실시한 조치를 중요도 순으로 정리해보았다.

내 경우 성능을 결정하는 5가지 지표 중 SI(Speed Index)가 가장 나쁘게 나왔고 그 아래 분석에서는
🔺 Eliminate render-blocking resources
이처럼 렌더링을 차단하는 리소스를 제거하라는 경고가 있었다.
공식 문서에서 말한 렌더링 차단 리소스는 아래와 같았다.

<script> 태그에 async/defer 추가내 경우 index.html에서 유일한 스크립트인 bundle.js를 가져오는 <script> 태그가 <head> 내부가 아닌 <body> 태그가 닫히기 직전에 잘 위치했으나, async/defer 속성이 없었다.
기본 스크립트와 async, defer 스크립트의 차이점을 알아보자.
브라우저는 html 파싱 중 기본 스크립트를 만나면 다운로드, 실행 완료할 때까지 html 파싱을 중단한다.
내 웹 페이지의 bundle.js 파일은 Mapbox-gl, html2canvas 같은 라이브러리까지 포함하고 있어 용량이 크다. 따라서 bundle.js를 다운, 실행하느라 html 파싱을 중단한 시간이 길어져 SI가 나빠진 것으로 보인다.
async 스크립트 : 브라우저가 html 파싱을 멈추지 않고 백그라운드에서 스크립트를 다운로드. 다운로드가 끝나는 순으로 실행, 실행 시에는 html 파싱 중단. defer 스크립트 : 브라우저가 html 파싱을 멈추지 않고 백그라운드에서 스크립트를 다운로드. html 파싱이 끝난 후 스크립트 위치한 순서대로 실행.내 웹 페이지에서는 JS로 UI 요소를 추가하는 코드가 많았기 때문에 화면이 완성되지 않은 채로 사용자에게 보여지는 것을 막기 위해 JS가 다운로드 되는대로 실행하는 async 속성을 추가했다.
두번째 렌더링 차단 리소스는 <link rel="stylesheet"> 태그이다.
브라우저는 스크립트와 마찬가지로 스타일시트를 만났을 때도 html 파싱을 중단하고 css 파일을 파싱하기 때문이다.
개발자도구에서 Cmd + Shift + P -> Show Coverage 를 입력하면 아래와 같은 창이 뜬다.

여기서 오른쪽 바를 통해 첫 페인트에서 필요하지 않은, 즉 중요하지 않은 부분이 차지하는 비율(빨간색 부분)이 어느 정도인지 확인할 수 있다.
CSS 파일 중 전체 용량이 가장 크고 중요하지 않은 코드 비율도 가장 큰(85%) 파일은 mapbox-gl.css이다.
따라서 mapbox-gl.css를 가져오는 스타일시트를 제거하고 대신 JS에서 동적으로 로드해주도록 했다. 코드는 아래와 같다.
<!-- index.html의 <head> 내부 -->
<link
rel="stylesheet"
href="https://api.mapbox.com/mapbox-gl-js/v3.10.0/mapbox-gl.css"
/>
대신
// Map.js
if (!document.querySelector('link[href*="mapbox-gl.css"]')) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://api.mapbox.com/mapbox-gl-js/v3.10.0/mapbox-gl.css";
document.head.appendChild(link);
}
❓ 갑자기 든 의문
async 스크립트는 다운로드 후 실행할 때는 html 파싱이 중단된다고 했는데, 그럼 JS로 동적으로 CSS 파일을 로드한다고 해도 결국 JS 실행 시 용량이 큰 CSS 파일을 가져오고 이는 html 파싱 중단으로 이어져 마찬가지로 렌더링이 차단되는 것 아닐까? 하는 의문이 생겼다.
-> 그러나 JS로 CSS 파일을 동적으로 로드하는 경우에document.head.appendChild(link)이 코드 자체는 즉시 실행되지만, 실제로 CSS 파일이 다운로드 및 파싱되는 것은 백그라운드에서 비동기적으로 일어난다고 한다. 따라서 스타일시트를 통해 가져오는 것과 달리 렌더링을 차단하지 않는 것이다. (개발자도구 Performance 탭에서 확인 가능)
내 웹페이지에서 화면을 가장 크게 차지하는 요소는 배경 이미지인데, 파일의 용량이 9.7MB로 매우 컸다. LCP(Largest Contentful Paint) 요소는 아니지만* 네트워크 요청 시간을 늘리는 등 간접적으로 성능 저하로 이어질 수 있기 때문에 이것 또한 조치 대상으로 보았다.
다양한 이미지 압축 사이트가 있는데 그중 sqoosh를 사용했다.

원본 이미지 업로드 후, 사이즈를 줄이고 디폴트인 MozJPEG으로 압축했다. 결과물은 jpg으로 나온다. (webp 형식을 권장하길래 그걸로도 압축해봤는데 MozJPEG보다 용량이 크게 나와서 그냥 MozJPEG으로 했다.)
리사이징, 압축하여 파일 크기 9.7MB -> 1.4MB로 축소,
요청 시간도 9초 가량에서 1.4초로 줄었다. (개발자도구 Network 탭에서 확인)
❓ 배경 이미지가 LCP 요소로 잡히지 않은 이유
우선 LCP(Largest Contentful Paint)란, 뷰포트 내에서 가장 큰 콘텐츠 요소가 렌더링되는 시점을 측정하는 항목이다.
나는 배경 이미지가 가장 크니까 당연히 LCP 요소로 잡힐 줄 알았는데, 상단의 문구(텍스트 블록)가 LCP 요소로 잡혔다. 알고보니 배경 이미지를 img 태그가 아니라 css의 background-image 속성으로 넣어서 콘텐츠가 아닌 스타일로 인식되기 때문이었다. 또한 img 태그로 넣더라도 배경으로 깔리게 되면(z-index 속성을 통해 다른 콘텐츠를 위로 올리면) 사용자에게 보이는 주요 콘텐츠로 인식되지 않아 마찬가지로 LCP 요소로 잡히지 않는다.
처음으로 성능 개선을 해봐서 그리 간단하지는 않았던 것 같다. 뒤늦게 본 lighthouse 공식 문서에 워낙 설명이 잘 되어 있어서 진작에 이거 보고 할 걸 하는 생각이 들었다.
성능을 결정하는 지표에 여러 가지가 있고, 성능 개선을 위해서는 각각의 지표들의 점수를 높여야 하는데 그 각각이 독립적이지도 않고 그렇다고 하나가 좋아지면 반드시 같이 좋아지는 것도 아니고, 또 무엇보다 같은 코드라도 측정할 때마다 성능 점수가 계속 바뀌어서 좀 어려웠던 것 같다. 공식문서에서도 네트워크 속도나 서버 반응 속도, 브라우저와 PC의 상태 등등에 의해 영향을 받아 바뀔 수 있으니 최소 5번 이상 측정해서 중앙값, 최소/최댓값 등을 보라고 얘기한다. (https://github.com/GoogleChrome/lighthouse/blob/main/docs/variability.md)
lighthouse 및 개발자 도구로 성능 측정 및 개선하는 방법뿐만 아니라 브라우저의 렌더링 과정과 어떤 요소에 의해 웹 사이트의 사용성이 나빠지는지를 알게되어 앞으로 개발할 때 그런 것들을 고려하며 코드를 짜게될 것 같다.