Core Web Vitals 90점 만들기
Lighthouse 62점이던 블로그를 90점까지 올린 과정을 숫자와 함께 기록한다
62점이 떴다
블로그 배포하고 처음으로 Lighthouse를 돌렸다. 모바일 성능 점수 62점. 데스크탑은 89점이라 괜찮았는데 모바일이 문제였다. LCP 3.8초, CLS 0.15, INP 320ms. 세 지표 전부 빨간색이었다.
개인 블로그니까 상관없다고 생각할 수도 있는데, 검색 엔진 순위에 영향을 미친다는 글을 읽고 나니 무시가 안 됐다. (근데 그 글도 어디서 봤는지 기억이 안 난다.)
LCP부터 잡았다: 3.8초에서 1.2초
범인은 히어로 이미지랑 웹폰트였다.
히어로 이미지를 Next.js Image 컴포넌트로 바꾸고 priority 붙였다. 이것만으로 1.5초 빨라졌다. WebP로 변환하니 이미지 크기가 320KB에서 85KB로 줄었다. 웹폰트는 next/font로 자체 호스팅으로 바꿨다. Google Fonts CDN 외부 요청 레이턴시가 사라지면서 0.4초 추가 단축. 이 두 가지로 LCP가 3.8초에서 1.2초.
CLS: 의외로 쉬웠다
이미지에 명시적인 width, height 지정하고, 로딩 전에 placeholder 영역만 잡아뒀다. 그것만으로 CLS가 0.05 아래로 떨어졌다. 블로그에 광고가 없어서 이 부분은 상대적으로 수월했다. 광고 있는 서비스였으면 CLS 잡는 게 훨씬 고통스러웠을 거다. 동적으로 크기 바뀌는 광고 배너는 CLS의 최대 적이니까.
INP가 제일 까다로웠다
INP(Interaction to Next Paint)가 320ms. 원인을 찾아보니 다크모드 토글이랑 검색 모달이었다.
다크모드 토글을 누르면 전체 테마 CSS가 교체되면서 리페인트가 일어나고 다른 인터랙션이 블로킹됐다. CSS 변수 기반으로 바꾸니 토글 반응이 280ms에서 40ms로 줄었다. 검색 모달은 열릴 때 전체 포스트를 필터링하는 로직이 메인 스레드에서 돌고 있었다. useDeferredValue로 디바운싱하고 초기 로딩을 lazy로 바꿨더니 INP가 80ms 아래로. (이 작업이 제일 오래 걸렸다. 이틀.)
안 쓰는 코드 지우는 게 제일 효과 좋았다
Performance 탭에서 보니 초기 로딩 시 JS 파싱에 1.2초. 번들 분석기 돌려보니 안 쓰는 아이콘 라이브러리가 통째로 들어가 있었다. Lucide React 아이콘을 개별 import로 바꾸고, 안 쓰는 의존성 3개 지우니 번들이 180KB에서 112KB. 코드 최적화보다 안 쓰는 코드 지우는 게 효과가 압도적이다.
서드파티 스크립트도 미뤘다
Google Analytics랑 Giscus 댓글 위젯이 초기 로딩에 추가 요청을 보내고 있었다. 둘 다 strategy="lazyOnload"로 바꿨다. 사용자가 페이지를 어느 정도 본 뒤에 로드해도 문제없는 스크립트들이니까. 이것만으로 FCP가 0.3초 빨라졌다.
최종 점수
2주간 작업한 결과. LCP 3.8초 → 1.2초, CLS 0.15 → 0.03, INP 320ms → 75ms. Lighthouse 모바일 62점 → 94점. 무엇보다 내 블로그를 모바일로 열었을 때 체감이 확실히 달라졌다.
솔직히 실수한 게 있다
처음에 모든 지표를 동시에 잡으려고 했다가 시간을 낭비했다. LCP 하나만 고쳤어도 점수가 스무 점은 올랐을 거다. 가장 큰 병목부터 잡는 게 맞다. 완벽주의보다 우선순위가 중요하다. 그리고 Lighthouse 점수에 집착하지 말고 실제로 폰에서 열어보면 된다. 빠른지 안 빠른지 손가락이 안다.