
CLS 수치 0.31. 구글 서치 콘솔에서 경고 메일을 받았을 때 저도 처음엔 이게 뭔 숫자인지 몰랐습니다. 페이지가 로드되면서 뭔가 살짝 밀리는 느낌은 늘 있었는데, 그게 구글한테 측정되고 있었다는 걸 그날 처음 알았습니다. 직접 수정해가며 깨달은 것들을 순서대로 정리했습니다.
CLS를 잡는 데 이미지 태그 하나가 이렇게 중요할 줄 몰랐습니다
CLS(Cumulative Layout Shift)란 페이지가 로드되는 동안 화면의 요소들이 얼마나 많이, 얼마나 크게 움직이는지를 수치로 나타낸 지표입니다. 쉽게 말해 "로딩 중에 화면이 얼마나 덜컹거리는가"를 측정하는 값인데, 구글은 0.1 이하를 좋음 기준으로 정의하고 있습니다(출처: Google Web.dev). 저는 0.31이라는 수치가 얼마나 심각한 건지 처음엔 감이 없었는데, 실제로 모바일에서 글을 읽다가 버튼을 누르려는 순간 페이지가 쿵 내려앉는 경험을 독자가 반복하고 있었다는 걸 나중에 이해했습니다. 내가 독자라도 짜증이 났을 겁니다.
저는 PageSpeed Insights로 원인을 추적했습니다. 진단 항목 중 "이미지 요소에 명시적인 너비 및 높이가 없음"이라는 항목이 눈에 띄었고, 범인은 썸네일 이미지들이었습니다. 브라우저는 이미지 크기를 미리 알 수 없으면 자리를 비워두지 않습니다. 이미지가 뒤늦게 로드되면서 그 아래 텍스트가 밀려 내려가는 구조였던 거죠. 이걸 알고 나서야 "아, 그래서 글 읽다가 광고 클릭이 됐던 게 이 때문이었구나" 싶었습니다. 독자가 악의 없이 광고를 클릭한 게 아니라, 레이아웃이 밀리는 바람에 원하지 않는 곳을 누른 경우가 있었을 거라는 생각도 들었습니다.
수정은 생각보다 단순했습니다. 이미지 태그에 width와 height 속성을 HTML에 명시하고, CSS에 aspect-ratio를 추가했습니다. 여기서 aspect-ratio란 이미지나 컨테이너의 가로 대 세로 비율을 CSS로 사전 지정하는 속성으로, 실제 이미지가 로드되기 전에 브라우저가 해당 공간을 미리 확보해두도록 합니다. 이 두 가지를 적용하고 나서 CLS가 0.31에서 0.08로 떨어졌습니다. 속성 몇 개 추가한 것뿐인데 수치가 이만큼 바뀌는 건 솔직히 예상 밖이었습니다. 개발자 도구를 열어보기 전까지는 이미지 태그 속성 하나가 이 정도 영향을 줄 거라고 생각 못 했습니다.
광고 슬롯에 min-height를 지정하는 방식은 처음에 "그게 효과가 있나?" 싶었는데, 실제로 적용해보면 광고가 뜨기 전후로 콘텐츠가 튀는 현상이 눈에 띄게 줄어들었습니다. 동적으로 삽입되는 광고는 크기를 예측하기 어렵기 때문에 최솟값을 미리 잡아두는 것만으로도 CLS 점수가 달라집니다. 이 항목은 오히려 이미지보다 쉬운 수정인데, 많은 분들이 이미지에만 집중하고 광고 슬롯은 그냥 두는 경우가 많습니다.
히어로 이미지 하나 바꿨더니 LCP가 0.8초 줄었습니다
LCP(Largest Contentful Paint)란 페이지에서 가장 큰 콘텐츠 요소가 화면에 렌더링되기까지 걸리는 시간을 의미합니다. 구글이 정한 좋음 기준은 2.5초 이내이며, 이 수치가 SEO 순위에 직접 영향을 미친다는 점에서 무시하기 어려운 지표입니다(출처: Google Search Central). 처음에는 LCP를 개선하려면 이미지 파일 자체를 최적화해야 한다는 생각이 강했습니다. 물론 그것도 맞지만, 더 근본적인 문제가 있었습니다.
제 사이트의 히어로 영역은 CSS background-image 방식으로 배경 이미지를 설정하고 있었습니다. 이 방식이 문제가 되는 이유를 알고 나서는 꽤 허탈했습니다. background-image는 브라우저가 HTML을 파싱하고 CSS까지 처리한 뒤에야 이미지 로드 요청을 보냅니다. 반면 img 태그는 HTML 파싱 단계에서 바로 요청이 시작됩니다. 같은 이미지인데 어떻게 불러오느냐에 따라 로드 순서 자체가 달라지는 겁니다. 이걸 알기 전까지 저는 이미지 용량 줄이는 것에만 집중하고 있었는데, 이미 한계에 달할 때까지 줄여도 LCP가 거의 안 줄었던 이유가 여기에 있었습니다.
그래서 히어로 영역을 CSS background-image에서 img 태그로 전환했습니다. 거기에 fetchpriority="high" 속성을 붙였습니다. 여기서 fetchpriority란 브라우저에게 이 리소스를 다른 것보다 먼저 불러오라고 우선순위를 명시하는 HTML 속성입니다. 네트워크 요청이 동시에 몰릴 때 중요한 이미지가 뒤로 밀리지 않게 막아주는 역할을 합니다. 결과적으로 LCP가 약 0.8초 단축됐습니다. 속성 하나의 차이가 이 정도 영향을 준다는 게 제 경험상 꽤 인상적인 부분이었습니다.
크리티컬 CSS 분리도 LCP에 효과적이라고 알려져 있어서 시도해봤습니다. 크리티컬 CSS란 페이지 첫 화면에 렌더링되는 데 꼭 필요한 CSS만 따로 추출해 HTML에 인라인으로 삽입하는 기법으로, 외부 CSS 파일을 기다리지 않고 즉시 첫 화면을 그릴 수 있게 해줍니다. 원리는 맞는데, 관리 비용이 생각보다 높았습니다. 페이지 구조가 조금만 바뀌어도 크리티컬 CSS를 다시 골라내야 했고, 자동화 도구 없이 수동으로 유지하는 건 현실적으로 한계가 있었습니다. 이건 자동화 환경이 갖춰진 경우에 도입하는 게 맞다고 봅니다. 개인 블로그 운영자가 매번 수동으로 크리티컬 CSS를 관리하는 건 솔직히 지속하기 어렵습니다.
top 대신 transform, 이 차이가 체감으로 잡혔습니다
INP(Interaction to Next Paint)란 사용자가 버튼을 클릭하거나 키를 입력했을 때 화면이 얼마나 빠르게 반응하는지를 측정하는 지표입니다. 기존에 FID(First Input Delay)가 담당하던 역할을 대체한 것으로, 단순히 첫 입력만 보는 게 아니라 페이지 전체 사용 흐름에서의 반응성을 평가한다는 점이 다릅니다. 솔직히 이 지표는 저도 가장 늦게 신경 쓴 부분이었습니다. CLS와 LCP가 더 가시적이고 직관적이라서 INP는 잘 안 보이는 영역에 있었습니다.
저는 카드 컴포넌트 호버 애니메이션에 top: -6px을 쓰고 있었습니다. 이걸 transform: translateY(-6px)으로 바꿨을 때, 낮은 사양 기기에서 애니메이션이 눈에 띄게 부드러워졌습니다. 이유는 렌더링 파이프라인의 차이 때문입니다. top 같은 레이아웃 속성은 값이 바뀔 때마다 브라우저가 주변 요소들의 위치까지 다시 계산하는 레이아웃(reflow) 단계를 거칩니다. 반면 transform은 레이아웃 계산을 건너뛰고 GPU에서 합성(compositing) 단계만 처리하기 때문에 훨씬 가볍습니다. 이론으로는 알고 있었는데, 실제 기기에서 비교해보기 전까지는 체감이 잘 안 됐습니다. 오래된 중저가 폰을 하나 테스트 기기로 써보시는 걸 강력히 권합니다. 그 차이가 손가락으로 느껴집니다.
INP 수치 변화도 측정해봤는데, 특히 스크롤 중에 버튼을 클릭했을 때 반응이 더 빨라졌다는 게 체감으로 잡혔습니다. 측정 수치가 아니라 손가락으로 느껴지는 차이가 있으면, 그게 제일 확실한 개선 신호라고 생각합니다. CSS 한 줄의 차이가 INP에도 영향을 준다는 걸 이번에 다시 확인했습니다. transform 전환은 작업 자체는 간단하지만 효과는 분명했습니다.
Core Web Vitals 개선은 대규모 리팩터링 없이도 시작할 수 있습니다. 저는 이미지 태그 속성 추가, 히어로 이미지 방식 전환, CSS 애니메이션 속성 교체라는 세 가지 변경만으로 CLS, LCP, INP 수치를 모두 개선했습니다. 먼저 PageSpeed Insights로 본인 사이트를 진단하고, 경고로 표시된 항목부터 하나씩 처리해보시길 권합니다. 크리티컬 CSS 같은 복잡한 최적화는 기본 항목을 다 잡은 뒤에 검토해도 충분합니다. 그리고 테스트 기기는 본인이 쓰는 최신 폰보다 중저가 기기 하나를 따로 두는 게 훨씬 현실적인 진단이 됩니다. 좋은 기기에서는 문제가 보이지 않는 경우가 너무 많습니다.
참고: Google Core Web Vitals 공식 문서: https://web.dev/vitals
PageSpeed Insights: https://pagespeed.web.dev
Google Search Central – CWV: https://developers.google.com/search/docs/appearance/core-web-vitals
MDN – content-visibility: https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility
web.dev – CLS 개선: https://web.dev/cls