Core Web Vitals 완벽 체크리스트 (2026)

LCP, INP, CLS 성능을 개선할 때 점검해야 할 모든 최적화 항목

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-10-17

Core Web Vitals 완벽 체크리스트

이 Core Web Vitals 체크리스트는 새 사이트를 배포하기 전, Largest Contentful Paint (LCP), Interaction to Next Paint (INP), Cumulative Layout Shift (CLS)를 개선할 때, 그리고 사이트에 대규모 변경이 있을 때 점검해야 할 모든 최적화 항목을 다룹니다. 페이지가 Google의 Core Web Vitals 평가를 통과할 만큼 충분히 빠르고 안정적인지 바로 확인할 수 있습니다.

새롭게 알게 된 내용을 반영하여 이 체크리스트를 항상 최신 상태로 유지합니다. 빠진 내용이 있습니까? 알려 주세요.

core web vitals lcp inp cls

Core Web Vitals 최적화 체크리스트

모바일 p75에서 기준을 통과하지 못한 지표부터 시작해 섹션별로 차례대로 점검하세요. 각 섹션마다 상세 가이드 링크가 있으므로, 코드를 변경하기 전에 해당 항목이 왜 중요한지 먼저 확인할 수 있습니다.

이미지 최적화

화면 viewport에 크게 보이는 이미지는 보통 Largest Contentful Paint 요소가 됩니다. 따라서 이미지를 최적화하는 것이 LCP를 개선하는 가장 효과적인 방법 중 하나입니다. 전체 전략은 LCP 이미지 최적화 방법에서 확인하세요.

  • 이미지는 화면에 표시되는 최대 크기에 맞게 조정하세요. 아무도 보지 않는 픽셀에 쓰이는 용량은 낭비일 뿐이며, 크기를 알맞게 조정한 이미지는 육안으로 보이는 화질 저하 없이도 50% 이상 용량을 줄일 수 있습니다. 작은 화면에는 반응형 이미지를 함께 적용하세요.
  • 첫 화면 아래에 있는 이미지는 lazy loading하세요. 방문자가 스크롤하여 근처에 도달할 때까지 브라우저가 다운로드를 미루므로, 초기 대역폭을 첫 화면 표시에 집중하여 First Contentful Paint (FCP)와 전체 로드 성능을 개선할 수 있습니다. LCP 이미지에는 절대 lazy loading을 적용하지 마세요. 브라우저가 레이아웃을 마칠 때까지 다운로드조차 시작하지 않습니다.
  • LCP 이미지에는 fetchpriority="high"를 지정하고, HTML에서 바로 드러나지 않는 경우에만 preload하세요. HTML 내부의 <img> 태그는 preload 스캐너가 알아서 찾아내므로, 이 속성만 추가해도 다운로드 우선순위가 큐 맨 앞으로 이동합니다. <link rel="preload" as="image" fetchpriority="high">를 사용한 preload는 CSS 배경 이미지나 JavaScript로 로드되어 브라우저가 HTML에서 직접 찾을 수 없는 LCP 이미지를 위한 방식입니다.
  • 모든 이미지에 width와 height를 지정하세요. 최신 브라우저는 이미지가 로드되기 전에 이 두 속성으로 종횡비를 계산하여 필요한 공간을 미리 확보하므로, 파일이 로드될 때 화면이 흔들리지 않습니다. 이렇게 하면 레이아웃 시프트의 주원인 하나를 없앨 수 있습니다.
  • JPEG나 PNG 대신 WebP나 AVIF를 사용하세요. 비슷한 화질에서 WebP 파일은 보통 JPEG보다 25~34% 작고, AVIF는 최대 50%까지 용량을 줄일 수 있습니다. fallback을 포함한 <picture> 요소를 사용하면 모든 브라우저를 완벽히 지원할 수 있습니다.
  • 네이티브 lazy loading을 사용하고 JavaScript 기반 lazy loading은 끄세요. loading="lazy" 속성을 사용하면 스크립트를 파싱하고 실행하는 과정 없이 동일한 작업을 수행합니다.
  • 이미지에 srcset을 지정하고, sizes 속성으로 표시 크기를 정밀하게 제어하세요. 브라우저가 기기에 맞는 최적의 이미지를 선택하므로, 모바일 기기가 데스크톱용 대용량 이미지를 다운로드하지 않게 됩니다.
  • decoding="async"를 추가하세요. 브라우저가 백그라운드에서 이미지를 병렬로 디코딩하므로, 디코딩 중에도 페이지의 나머지 영역을 계속 렌더링합니다.
  • 이미지 메타데이터를 제거하세요. EXIF 같은 데이터는 파일 용량만 늘릴 뿐이며, 이를 제거해도 이미지 화질에는 아무 영향이 없습니다. ImageOptim이나 Squoosh로 직접 제거하거나, 빌드 과정에서 Sharp를 사용해 자동으로 처리할 수 있습니다.
  • LCP 요소에 CSS 배경 이미지를 사용하지 마세요. 브라우저는 HTML의 <img>보다 CSS의 배경 이미지를 훨씬 늦게 발견합니다. 불가피한 경우라면 <link rel="preload"> 태그로 preload하여 브라우저가 일찍 발견하도록 만드세요. 자세한 내용은 LCP resource load delay에서 확인하세요.

웹 폰트 최적화

웹 폰트는 First Contentful Paint를 지연시키고, 레이아웃 시프트를 유발하며, 페이지 로드 초기의 부족한 대역폭을 두고 다른 파일과 경쟁합니다. 폰트 파일을 어디에 호스팅해야 하는지는 Google Fonts 자체 호스팅 가이드를 확인하세요.

  • 첫 페인트를 빠르게 처리하려면 @font-face 규칙에 font-display: swap을 설정하세요. 브라우저가 fallback 폰트를 즉시 보여주고 백그라운드에서 웹 폰트를 로드합니다. 로드가 완료되면 웹 폰트로 교체합니다. 자세한 내용은 웹 폰트 로드 중 텍스트가 계속 표시되도록 유지하는 방법을 참고하세요.
  • 폰트로 인한 레이아웃 시프트를 완전히 없애려면 font-display: optional과 preload를 함께 사용하세요. optional을 사용하면 브라우저는 웹 폰트를 약 100ms 동안만 기다립니다. 그때까지 로드되지 않으면 해당 페이지 뷰에서는 fallback 폰트를 그대로 유지하고 폰트를 교체하지 않습니다. preload를 적용하면 이 시간 안에 폰트가 도착할 확률이 훨씬 높아집니다.
  • font-face 디스크립터를 사용해 fallback 폰트의 크기를 웹 폰트와 일치시키세요. fallback 폰트에 size-adjust, ascent-override, descent-override, line-gap-override를 설정하면 웹 폰트로 교체될 때 텍스트가 거의 흔들리지 않아 CLS를 최소화할 수 있습니다.
  • 콘텐츠에 필요한 글자만 남기도록 폰트를 서브셋화하세요. 100KB가 넘는 전체 라틴 폰트도 알맞게 서브셋을 만들면 20KB 미만으로 줄어듭니다. Font Squirrel을 통해 온라인에서 서브셋을 만들 수 있고, 커맨드라인에서는 pyftsubset이나 glyphhanger를 사용할 수 있습니다.
  • 폰트 두께(weight)와 스타일 개수를 제한하세요. 두께가 하나 추가될 때마다 15~50KB의 다운로드 용량이 늘어납니다. 중요한 폰트는 최대 2개(보통 preload 적용)로 제한하고, 나머지 2개는 첫 렌더링 이후 늦게 로드되도록 구성하세요.

스크립트 최적화

스크립트는 Interaction to Next Paint 문제를 일으키고, Cumulative Layout Shift를 유발하거나 Largest Contentful Paint를 지연시킬 수 있습니다. 페이지 초반에 실행되는 작고 잘 작성된 스크립트조차 리소스를 두고 경쟁하며 페인트 지표(LCP 및 FCP)를 늦춥니다. 활용 가능한 모든 방법은 JavaScript 지연을 위한 14가지 방법에 정리되어 있습니다.

  • 불필요한 JavaScript는 제거하세요. Chrome DevTools의 Coverage 탭에서 사용하지 않는 코드를 확인할 수 있으며, 사용하지 않는 코드를 제거할 때마다 다운로드 시간과 main thread 시간을 절약할 수 있습니다.
  • 스크립트가 하는 역할에 따라 우선순위를 지정하세요. 화면 viewport에 큰 변화를 주는 스크립트는 render blocking이어야 합니다. 중요한 스크립트는 defer하거나 async로 로드하고, 필수가 아닌 스크립트는 브라우저가 유휴 상태가 될 때까지 기다리게 하세요. 상세 전략은 리소스 우선순위 지정 가이드에 나와 있습니다.
  • 코드를 분할하고 필요한 부분만 lazy load하세요. 대용량 JavaScript 번들을 필요한 시점에만 로드되는 작은 청크로 쪼개면 초기 로드가 훨씬 가벼워집니다. 최신 번들러(webpack, Rollup, esbuild)는 동적 import 사용 시 코드를 자동으로 분할합니다.
  • SWC, Terser, esbuild 같은 도구를 사용해 매번 JavaScript 파일을 압축하고 재컴파일하세요. 보통 파일 크기를 30~50% 줄일 수 있습니다.
  • 서드파티 스크립트를 제한하세요. 서드파티 스크립트는 추가 DNS 조회, 연결 오버헤드, main thread 점유를 유발하므로 정말 필요한지, 더 가벼운 대안은 없는지 검토해야 합니다. Chrome DevTools의 Network 패널에서 주기적으로 점검하세요.
  • 서드파티 스크립트는 async나 defer로 로드하세요. 서드파티가 언제 어떻게 동작할지 예측할 수 없으므로, 렌더링을 차단하게 둬서는 절대 안 됩니다.
  • 서드파티 스크립트가 실제 방문자에게 미치는 영향을 모니터링하세요. Long Animation Frames (LoAF) API나 CoreDash를 사용하면 실제 field data에서 서드파티 스크립트가 INP와 LCP에 미치는 영향을 확인할 수 있습니다. 서드파티 JavaScript에 대한 성능 예산을 설정하고 정기적으로 검토하세요.

스타일 최적화

스타일은 기본적으로 렌더링을 차단하므로, head 안의 모든 스타일시트는 First Contentful Paint를 지연시키고 LCP element render delay를 늘립니다. 스타일시트는 작게 분리하여 유지하고, CSS를 둘로 쪼개는 꼼수는 건너뛰세요. 사용하지 않는 스타일을 정리하는 팁은 사용하지 않는 CSS 제거 방법을 참고하세요.

  • CSS를 압축하세요. cssnano, PostCSS 또는 CSS 전처리기 내장 압축 기능을 사용해 공백과 주석을 제거하세요. 비용이 들지 않으니 무조건 적용해야 하지만, 몇 킬로바이트 정도 줄어들 뿐이라 방문자가 체감하기는 어렵습니다.
  • 페이지에서 사용하지 않는 CSS는 제거하세요. CSS가 줄어들면 다운로드와 파싱 부담도 줄어듭니다. PurgeCSS나 Chrome DevTools의 Coverage 탭을 활용하면 사용하지 않는 CSS를 쉽게 찾을 수 있습니다. 단, 이 작업은 자체 빌드 과정에서 직접 처리하세요. WP Rocket, Perfmatters, LiteSpeed 같은 플러그인의 'Remove Unused CSS' 기능은 끄는 것이 좋습니다. critical CSS만큼이나 많은 문제를 일으킵니다.
  • CSS를 인라인으로 넣지 말고, critical CSS도 건너뛰세요. 인라인 CSS는 모든 리소스가 기다려야 하는 HTML 파일의 크기를 키웁니다. 게다가 브라우저는 대개 HTML을 캐싱하지 못하므로 매 페이지 뷰마다 동일한 CSS를 다시 다운로드하게 됩니다. 반면 별도 스타일시트는 두 번째 페이지부터 캐시에서 바로 가져옵니다. critical CSS는 첫 방문 시 왕복 시간을 한 번 아껴주지만, 나머지 CSS가 첫 페인트 이후에 도착하면서 스타일 재계산, 리페인트, 그리고 흔히 레이아웃 시프트를 유발합니다.
  • render blocking 스타일시트는 압축 기준 각각 15KB 미만으로 유지하세요. 이 크기는 새 연결의 첫 번째 왕복에 들어갈 수 있는 용량이므로 목표 예산으로 삼아야 합니다. Slow 4G 환경의 워터폴을 보면 잘못된 두 가지 전형적인 사례가 드러납니다. 60KB짜리 스타일시트는 로드에 너무 오래 걸리고, 0.3KB짜리 파일이 줄지어 있으면 각각 요청 초기화 비용을 치르게 됩니다. 지나치게 작은 파일들은 합치고, 큰 파일은 쪼개거나 덜어내세요.
  • 습관적으로 CSS를 async로 로드하지 마세요. media="print" 꼼수는 첫 화면에서 전혀 쓰이지 않는다는 확신이 있는 CSS에만 유효하지만, 이를 확실히 장담할 수 있는 경우는 드뭅니다. 첫 화면용 CSS와 나머지 CSS를 나누는 것은 이름만 바꾼 critical CSS에 불과합니다.
  • CSS에서 모든 @import를 제거하세요. @import는 브라우저가 순차적으로 가져올 수밖에 없는 스타일시트 체인을 만들며, preload 스캐너는 체인 끝에 있는 파일을 미리 찾을 수 없습니다. HTML에서 직접 링크하거나 빌드 시 하나로 병합하세요.

리소스 힌트 최적화

리소스 힌트는 브라우저가 중요한 리소스를 먼저 다운로드하도록 돕습니다. preload된 파일은 힌트가 없을 때보다 훨씬 일찍 큐에 들어가 준비됩니다. 힌트를 잘 활용하면 LCP resource load delay를 줄일 수 있습니다. 고급 활용법은 103 Early Hints 가이드를 확인하세요.

  • 첫 화면에 필요하지 않은 리소스에 걸린 힌트는 제거하세요. 초기 대역폭은 부족하므로, 불필요한 preload나 연결 설정은 정작 중요한 파일에 쓰여야 할 대역폭을 빼앗아 갑니다.
  • 콘텐츠 전송 네트워크(CDN)나 폰트 제공업체처럼 초반에 필요한 도메인에는 미리 preconnect를 적용하세요. 해당 도메인으로 첫 요청이 나가기 전에 DNS 조회, TCP 연결, TLS 핸드셰이크가 미리 완료됩니다. 중요한 서드파티 오리진에는 <link rel="preconnect" href="https://example.com">를 사용하세요.
  • preconnect 적용이 부담스러운 도메인에는 DNS prefetch를 고려하세요. preconnect는 전체 연결을 구성하지만, <link rel="dns-prefetch">는 도메인 이름만 미리 확인해 둡니다. 나중에 필요할 가능성이 있지만 아직 완전한 연결 비용을 치를 단계가 아닌 도메인에는 이것으로 충분합니다.
  • LCP 이미지가 HTML에 직접 포함되어 있지 않다면 preload하세요. LCP는 주요 콘텐츠가 표시되는 데 걸리는 시간을 측정하며, LCP 요소를 preload하면 해당 리소스를 큐의 맨 앞으로 당겨옵니다. 이미 HTML에 들어 있는 <img>라면 추가 태그 없이 fetchpriority="high"만으로 동일한 효과를 냅니다.
  • 중요한 폰트는 <link rel="preload" as="font" type="font/woff2" crossorigin>로 preload하세요. 브라우저가 일찍 폰트를 가져오므로 텍스트가 더 빨리 표시되고 폰트 교체로 인한 레이아웃 시프트도 줄어듭니다.
  • 가능하다면 리소스 힌트를 103 Early Hints로 전송하세요. 103을 사용하면 서버가 전체 응답을 준비하기 전에 힌트를 먼저 전송하므로, 브라우저가 리소스 정보를 가장 빠르게 전달받을 수 있습니다. 서버에서 103을 지원하지 못하면 Link 응답 헤더를 사용하고, 헤더 사용도 불가능하다면 fallback으로 페이지 <head>에 <link> 요소를 추가하세요.
  • CSS가 폰트를 발견하기 전에 미리 preload하세요. CSS에서 참조하는 폰트는 해당 CSS가 다운로드되고 파싱된 후에야 발견됩니다. HTML <head>에서 preload하면 이 대기 시간을 없애 폰트를 병렬로 다운로드할 수 있으며, FCP와 레이아웃 시프트 위험을 모두 낮출 수 있습니다.

아이콘 최적화

아이콘은 과도하게 사용하기 쉽습니다. 페이지에서 아이콘을 5개만 쓰면서 전체 아이콘 세트를 로드하는 경우가 대표적입니다. 용량이 큰 인라인 SVG 아이콘은 HTML을 비대하게 만들고, 아이콘 폰트는 쓰지도 않을 수천 개의 글리프를 포함하곤 합니다. 아이콘을 제대로 최적화하면 로드할 HTML과 CSS가 줄어 LCP에 도움이 되고, 각 아이콘의 공간을 미리 확보하여 CLS를 개선할 수 있습니다.

  • 용량이 큰 SVG 아이콘을 HTML에 인라인으로 넣지 마세요. HTML 용량이 늘어나 페이지가 느려지고, 브라우저가 아이콘만 따로 캐싱할 수도 없습니다. 외부 SVG 스프라이트 시트가 성능과 유연성 사이에서 대개 가장 균형 잡힌 선택입니다. 개별 파일로 분리하거나 (주의 깊게 다룬다면) 아이콘 폰트를 사용하는 것도 HTML을 작게 유지하는 방법입니다.
  • Font Awesome 같은 대용량 아이콘 세트 전체를 그대로 로드하지 마세요. 전체 세트는 100KB가 넘을 수 있지만, 아이콘 20개만 추린 서브셋은 5KB 미만으로 유지할 수 있습니다. 필요한 것만 골라 작은 아이콘 폰트로 서브셋화하거나 개별 SVG를 사용하세요.
  • 이미지와 마찬가지로 아이콘에도 width와 height를 예약해 두세요. 그래야 브라우저가 공간을 비워 두어 아이콘이 로드될 때 레이아웃 시프트가 일어나지 않습니다. SVG 요소에 width와 height 속성을 지정하거나 CSS로 명시적인 크기를 설정하세요.
  • 첫 화면에 필요하지 않은 아이콘은 preload하거나 인라인으로 넣지 마세요. 핵심 콘텐츠가 먼저 로드되어야 합니다. 첫 화면 아래에 있는 아이콘은 lazy load하세요.

서버 응답 시간 최적화

Time to First Byte (TTFB)로 측정되는 서버 응답 시간은 모든 페인트 지표보다 앞서 발생하므로, 서버가 느리면 그 뒤의 모든 과정이 지연됩니다. 상세 전략은 TTFB 문제 진단 및 성능을 위한 Cloudflare 설정 가이드를 확인하세요.

  • 인프라가 탄탄하고 빠르며 신뢰할 수 있는 호스팅 업체를 선택하세요. 실제 환경에서 직접 측정한 TTFB를 기준으로 호스팅 업체를 비교해야 합니다. 마케팅에 나오는 수치는 가공된 데이터일 뿐입니다.
  • 코드와 데이터베이스 쿼리의 실행 시간을 자주 기록하세요. 쿼리 프로파일링과 애플리케이션 성능 모니터링(APM) 도구를 사용하면 느린 엔드포인트를 즉시 짚어낼 수 있습니다.
  • 자주 요청되는 데이터를 매번 다시 가져오지 않도록 브라우저와 서버에 캐싱하세요. 전체 페이지 캐싱을 적용하면 몇 초씩 걸리던 TTFB를 100ms 미만으로 줄일 수 있습니다. 자세한 내용은 캐시 유효 기간 최적화 가이드를 참고하세요.
  • 작은 개인화 영역은 클라이언트나 엣지에서 렌더링하세요. 장바구니 수량, 로그인 상태, 사소한 메뉴 변경 때문에 페이지 전체 캐시를 날려서는 안 됩니다. 전체 페이지 캐시는 그대로 유지하고, 이런 세부 사항만 클라이언트 사이드나 엣지 렌더링으로 채우세요.
  • 웹 서버를 튜닝하세요. keep-alive 설정, 워커 프로세스 수, 메모리 할당, timeout 값을 점검해야 합니다. 설정이 잘못된 서버는 리소스를 낭비하고 응답 속도를 떨어뜨립니다.
  • 콘텐츠 전송 네트워크(CDN)를 사용하세요. 방문자와 가까운 엣지 노드(서버)에서 정적 콘텐츠를 제공하므로, 데이터 이동 거리가 줄어들어 전 세계 사용자의 로드 속도가 빨라집니다. 또한 CDN은 대개 자체 서버보다 최적화 설정이 잘 되어 있습니다. 실질적인 설정 단계는 Cloudflare 설정 가이드를 확인하세요.
  • 요청당 처리하는 작업량을 줄이세요. 비용이 큰 연산은 미리 처리해 두고, 효율적인 알고리즘을 사용하며, 필수가 아닌 작업은 백그라운드 작업으로 넘기세요. 그런 다음 애플리케이션의 요청 수명 주기를 다시 점검하여 불필요한 단계를 모두 제거하세요.
  • HTTP/3를 사용하세요. TCP와 TLS 조합은 연결 설정에 둘 이상의 왕복이 필요한 반면, QUIC은 단 한 번의 왕복으로 연결을 맺으며 패킷이 유실되어도 동일 연결의 다른 요청을 차단하지 않습니다. 모바일 네트워크에서 이 효과를 가장 크게 체감할 수 있습니다. 성능 이득이 연결 과정에서 발생하므로 TTFB에 바로 반영되고, 결과적으로 LCP까지 개선됩니다. 브라우저는 Alt-Svc 헤더나 HTTPS DNS 레코드를 통해 비로소 HTTP/3 지원 여부를 알게 되므로, 첫 방문은 여전히 HTTP/2로 처리되는 경우가 많습니다. 자세한 내용은 연결 지속 시간 최적화를 참고하세요.
  • Server-Timing 헤더를 추가하세요. 페이지를 처리할 때 서버 작업의 각 단계가 얼마나 걸렸는지 알려주므로, 병목 구간을 파악하고 Largest Contentful Paint (LCP)를 지연시키는 부분을 개선할 수 있습니다. 이 정보는 Chrome DevTools의 Network 패널에서 확인할 수 있으며, CoreDash 같은 RUM 도구로 수집할 수도 있습니다.
  • 느린 데이터베이스 쿼리를 로깅하고 주기적으로 해결하세요. 슬로우 쿼리 로깅을 켜고(모든 주요 데이터베이스에서 지원합니다) 매주 로그를 확인하세요. 인덱스를 개선하고, 쿼리를 재작성하며, 빈번한 쿼리에 캐시 계층을 두면 TTFB를 크게 줄일 수 있습니다.
  • 텍스트 파일(HTML, CSS, JavaScript)은 GZIP이나 그보다 뛰어난 Brotli로 압축하세요. 서버가 전송 직전에 실시간으로 압축하므로 파일 크기를 약 70% 줄일 수 있습니다. Brotli는 일반적으로 GZIP보다 압축률이 15~20% 더 우수합니다.

상호작용 최적화

Interaction to Next Paint (INP)는 사용자 상호작용에 사이트가 얼마나 빠르게 반응하는지 측정합니다. INP가 나쁜 원인은 대개 main thread를 차단하는 긴 JavaScript 작업 때문입니다. INP의 3단계 각각에 대해 별도 가이드가 마련되어 있습니다: input delay, processing time, presentation delay.

  • 실행 비용이 큰 스크립트에는 'idle-until-urgent' 패턴을 적용하세요. 중요한 작업을 먼저 실행하고 필수적이지 않은 JavaScript는 main thread가 유휴 상태가 될 때까지 기다리게 하면, 오래 실행되는 스크립트 때문에 렌더링과 사용자 입력이 지연되는 일을 막을 수 있습니다. 급하지 않은 작업은 requestIdleCallback으로 예약하고, 결과가 더 빨리 필요해지면 즉시 실행하세요. 자세한 내용은 processing time 최적화에서 확인하세요.
  • main thread에 yield하여 long task를 쪼개세요. long task는 main thread를 차단하여 페이지 반응을 멈추게 만듭니다. 작업을 더 작은 단위로 나누고 그 사이마다 브라우저에 제어권을 넘기면, 브라우저가 그 틈에 클릭이나 키 입력을 처리할 수 있습니다. 지원되는 환경에서는 scheduler.yield()를 사용하고, 그렇지 않다면 setTimeout(0)을 사용하세요. 자세한 내용은 JavaScript 스크롤을 제거하여 INP를 개선하는 방법 가이드를 참고하세요.
  • 무거운 작업을 실행하기 전에 시각적 피드백을 먼저 렌더링하세요. INP는 다음 페인트까지의 시간을 측정하므로 버튼 상태를 변경하거나 스피너를 먼저 표시한 뒤, yield(scheduler.yield() 또는 setTimeout)하고 나서 무거운 작업을 실행해야 합니다. :active 스타일이나 CSS transition은 main thread에 여유가 생겨 페인트를 할 수 있을 때 비로소 화면에 나타납니다. 핸들러가 스레드를 계속 붙잡고 있으면 피드백은 물론 페이지 전체가 멈춥니다.
  • 스크롤과 터치 이벤트 리스너에 { passive: true }를 지정하여 패시브 리스너로 만드세요. 핸들러가 preventDefault()를 호출하지 않는다는 점을 브라우저에 알려주므로, JavaScript 처리를 기다리지 않고 즉시 스크롤을 시작할 수 있습니다. 모바일에서 이 차이를 가장 크게 체감할 수 있습니다. 스크롤 자체가 INP가 측정하는 상호작용은 아니므로, 이는 INP 수치 자체보다는 부드러운 스크롤 체감을 위한 것입니다.

Core Web Vitals 모니터링

Core Web Vitals를 지속적으로 모니터링하세요. 그래야 성능 저하를 일찍 발견하고 수정 작업이 예상대로 효과를 냈는지 확인할 수 있습니다. 중요한 것은 p75 기준의 field data이며, lab test는 그 원인을 파악하는 데 쓰입니다. 모바일 환경의 각 지표 p75 값, Search Console에서 지적한 페이지 그룹, 최근 몇 주간의 추세부터 살펴보세요. 수치가 갑자기 급락했다면 대개 특정 배포가 원인이고, 서서히 나빠진다면 서드파티 스크립트가 하나씩 누적된 탓일 가능성이 큽니다.

  • Lighthouse를 정기적으로 실행하세요. Lighthouse는 페이지의 성능 문제를 찾아내는 Google의 무료 오픈소스 감사 도구입니다. 실제 방문자가 겪는 방식 그대로 Core Web Vitals를 측정하지는 못하지만, 매번 표준화된 동일한 조건에서 사이트를 테스트하고 비교하기에는 훌륭한 도구입니다. 일반적인 Lighthouse 테스트 중에는 아무도 클릭하지 않으므로 INP를 전혀 측정하지 못하며, Total Blocking Time이 가장 근접한 대체 지표입니다. Lighthouse를 CI/CD 파이프라인에 통합하면 성능 저하가 실제 서비스에 배포되기 전에 미리 잡아낼 수 있습니다.
  • CrUX 히스토리를 정기적으로 확인하세요. CrUX(Chrome User Experience Report)는 실제 환경의 사용자 경험 성능을 담은 Google의 공개 데이터 세트이며, Google이 Core Web Vitals 통과 여부를 판단할 때 사용하는 데이터입니다. CrUX의 모든 수치는 최근 28일간의 p75 값이므로, 성능 저하는 몇 주에 걸쳐 서서히 반영되고 수정 결과가 온전히 드러나는 데도 약 4주가 걸립니다. PageSpeed Insights는 최근 28일간의 데이터만 보여주므로, 이전 히스토리는 CrUX Vis나 CrUX History API를 통해 확인해야 합니다.
  • RUM 트래킹을 구축하세요. Real User Monitoring(RUM)은 방문자의 실제 위치와 기기 환경에서 페이지가 실제로 얼마나 걸리는지 측정합니다. Lighthouse는 lab test이고, CrUX는 최근 28일간의 p75 수치만 제공할 뿐 어떤 요소나 스크립트가 문제를 일으켰는지는 알려주지 않습니다. RUM이 이 빈틈을 채워줍니다. 세부적인 Core Web Vitals 원인 분석 데이터를 위해 직접 개발한 CoreDash를 추천합니다.
  • 성능 예산을 설정하세요. 모바일 p75 기준으로 LCP 2.5초 미만, INP 200ms 미만, CLS 0.1 미만처럼 각 지표별 구체적인 목표를 정해야 합니다. 이 목표를 기준으로 정기적으로 측정하면 어떤 영역을 먼저 손봐야 하는지 바로 확인할 수 있습니다.
  • 데이터를 세그먼트별로 나누어 분석하세요. 중요한 방문자 유형과 페이지 유형은 별도로 추적해야 합니다. 전체 트래픽 평균에 묻혀 특정 사용자에게만 발생하는 문제가 가려질 수 있기 때문입니다. 먼저 기기와 페이지 템플릿별로 분류하고, 그다음 접속 속도와 국가별로 세분화하세요.

중요 렌더링 경로 최적화

중요 렌더링 경로는 브라우저가 HTML, CSS(및 JavaScript)를 화면의 픽셀로 변환하기 위해 거치는 모든 단계를 말합니다. 이 경로를 단축할수록 First Contentful Paint가 빨라지고 LCP element render delay가 줄어듭니다. 과도한 DOM 크기 방지 방법도 함께 확인하세요.

  • 중요 리소스 수를 최소화하세요. CSS나 동기식 JavaScript 같은 모든 render blocking 리소스는 브라우저가 화면을 그리기 전에 반드시 다운로드되고 처리되어야 합니다. 필수적이지 않은 스크립트는 defer하고 스타일시트는 작고 적게 유지하세요.
  • 로드 순서를 올바르게 설정하세요. 스타일시트를 맨 먼저 로드하고, 그다음 LCP 이미지와 첫 화면에 쓰이는 폰트, 이어서 첫 화면의 다른 이미지들, 마지막으로 지연된 스크립트 순서여야 합니다. fetchpriority 속성과 리소스 우선순위 힌트를 사용해 브라우저에 무엇이 가장 중요한지 알려주세요.
  • DOM 트리를 얕게 유지하세요. 깊은 중첩 구조는 스타일 계산과 레이아웃 작업을 늘립니다. 최대 32단계 깊이 이내, 전체 요소 수는 1,500개 미만을 목표로 삼으세요. 평평한 DOM 구조일수록 더 빠르게 화면을 그리고 INP presentation delay를 줄이는 데 도움이 됩니다.
  • 선택자에는 요소 태그나 속성보다 클래스와 ID를 우선 사용하세요. p.important 대신 .important로 작성하면 브라우저가 스타일 매칭 시 확인할 대상이 줄어들어 스타일 재계산 속도가 빨라집니다.
  • 선택자를 깊게 중첩하지 마세요. 깊이가 한 단계 늘어날 때마다 브라우저가 할 일도 늘어납니다. HTML 구조를 개편해 중첩을 줄이거나 해당 요소에 직접 명확한 클래스를 부여하여 중첩을 최대 3단계 이하로 유지하세요.
  • 자손 선택자 사용을 줄이세요. .container .content 같은 선택자를 쓰면 브라우저는 모든 .content 요소의 조상 노드를 일일이 거슬러 올라가 컨테이너를 확인해야 합니다. 콘텐츠 요소에 클래스를 직접 부여하면 매칭 속도가 훨씬 빠릅니다.
  • 동일한 스타일을 공유하는 선택자는 하나의 클래스로 그룹화하거나 BEM(Block Element Modifier) 네이밍 방식을 도입하세요. 유지 관리가 쉬워지고 CSS 용량도 줄어듭니다.

쿠키 동의 배너 최적화

GDPR 및 유사 규정으로 인해 쿠키 동의 배너가 필요하지만, 무신경하게 로드된 배너는 LCP를 지연시키고 CLS를 유발하며 INP를 악화시킬 수 있습니다. 자세한 내용은 Core Web Vitals를 위한 서드파티 위젯 최적화 가이드를 확인하세요.

  • 어차피 페이지를 서버에서 렌더링한다면 동의 배너도 서버에서 렌더링하세요. 초기 HTML 응답에 포함되어 전달되므로 추가 네트워크 요청이나 스크립트 실행 평가가 없어 별도의 JavaScript 솔루션보다 대개 훨씬 빠릅니다.
  • 캐싱된 페이지에서는 동의 스크립트를 async로 로드하고 fetchpriority="high" 지정을 고려하세요. 그래야 방문자가 상호작용하기 전에 배너가 제때 표시됩니다.
  • 배너는 position을 fixed로 지정하여 오버레이 형태로 띄우세요. 페이지 상단에 배너를 삽입하면 그 아래의 모든 요소를 아래로 밀어내어 첫 방문마다 레이아웃 시프트가 발생합니다. 화면 하단에 고정하면 콘텐츠 위에 얹히므로 레이아웃이 흔들리지 않습니다.
  • 가능하다면 쿠키 알림 스크립트와 스타일시트를 자체 호스팅하고 캐싱하세요. 동의 관리 플랫폼을 향한 DNS 조회와 연결 과정을 건너뛸 수 있고, 로드 동작도 직접 제어할 수 있습니다.

싱글 페이지 애플리케이션 최적화

React나 Vue로 구축한 싱글 페이지 애플리케이션(SPA)은 고유한 Core Web Vitals 문제를 겪습니다. 클라이언트 사이드 렌더링은 FCP와 LCP를 모두 지연시키며, hydration 과정은 INP를 가로막을 수 있습니다.

  • 항상 서버 사이드 렌더링이나 사전 렌더링을 사용하세요. 클라이언트에서만 렌더링되는 SPA는 화면에 무언가 보이기 전에 브라우저가 방대한 JavaScript를 다운로드하고 실행해야 합니다. SSR(Next.js, Nuxt, SvelteKit)이나 정적 prerendering을 적용하면 브라우저가 즉시 그릴 수 있는 HTML을 보낼 수 있습니다.
  • 동적 렌더링보다 정적 prerender를 우선시하세요. 정적 prerender는 빌드 타임에 생성되어 서버 처리 없이 CDN에서 바로 전송되므로, 요청마다 생성하는 방식보다 훨씬 빠릅니다. 요청별 데이터가 필요하지 않은 모든 페이지에는 정적 생성을 적용하세요.
  • 서드파티 스크립트는 hydration 이후에 로드하세요. hydration 과정은 페이지만 상호작용 가능하게 만드는 데도 이미 많은 main thread 시간을 소모하며, 이 순간 서드파티 스크립트까지 함께 실행되면 input delay가 악화됩니다. 필수적이지 않은 모든 스크립트는 hydration이 끝날 때까지 뒤로 미루세요.

과도한 DOM 크기 방지

비대한 DOM(1,500개 이상의 요소 또는 32단계를 넘는 깊이)은 더 많은 메모리를 사용하고, 스타일 계산을 늦추며, 비용이 큰 레이아웃 리플로우를 유발합니다. 이는 INP presentation delay와 페인트 지표에 그대로 드러납니다. 자세한 내용은 과도한 DOM 크기 해결 방법을 참고하세요.

  • 불필요한 DOM 요소는 제거하세요. 스타일이나 구조에 아무런 역할도 하지 않는 래퍼 요소를 찾아내고, 깊게 중첩된 <div> 구조를 시맨틱 HTML로 교체하세요. 길이가 긴 목록은 react-window나 virtual-scroller 같은 라이브러리로 가상화하여 실제 렌더링되는 DOM 크기를 작게 유지할 수 있습니다.
  • 선택자와 DOM 쿼리를 효율적으로 유지하세요. 복잡한 CSS 선택자와 광범위한 JavaScript 쿼리(예: 포괄적인 패턴의 querySelectorAll)는 DOM이 커질수록 점점 더 느려집니다. 구체적인 클래스 선택자를 사용하고, 가능한 한 각 쿼리의 탐색 범위를 특정 하위 트리로 한정하세요.
  • 화면 밖에 있는 콘텐츠에는 content-visibility: auto를 사용하세요. 브라우저는 해당 요소가 스크롤되어 화면에 들어올 때까지 렌더링을 건너뛰므로, 섹션이 긴 페이지의 초기 렌더링 작업을 대폭 줄일 수 있습니다.

API 요청 최적화

렌더링을 차단하거나 콘텐츠 표시를 지연시키는 API 요청은 LCP와 TTFB를 악화시킵니다. 특히 싱글 페이지 애플리케이션에서는 클라이언트가 가져오는 데이터가 느린 LCP의 주된 원인 중 하나입니다.

  • API 요청 횟수를 줄이세요. 요청이 늘어날 때마다 페이지 로드에 부담이 더해지므로 첫 화면에 정말 필요한 데이터가 무엇인지 확인하고 나머지는 쳐내세요. 여러 요청을 하나로 묶는 배칭이나 GraphQL을 활용하면 네트워크 왕복 횟수를 줄일 수 있습니다.
  • API 자체의 속도를 높이세요. API 설계와 구축 방식은 이를 기다리는 페이지 성능에 직결됩니다. 자주 요청되는 데이터는 API 서버 단에 캐싱을 적용하세요.
  • 핵심 API 요청은 <link rel="preload" as="fetch" crossorigin>로 preload하세요. 이미지 preload와 마찬가지로 요청을 조기에 시작하여 첫 렌더링에 필요한 데이터를 미리 준비할 수 있습니다. crossorigin 속성을 빠뜨리면 preload가 실제 fetch() 호출과 매칭되지 않아 브라우저가 데이터를 중복 다운로드하게 됩니다. 더 많은 기법은 리소스 우선순위 지정 가이드에서 확인하세요.

채팅 위젯 최적화

채팅 위젯은 레이아웃 시프트를 자주 일으키며, 너무 일찍 로드되면 LCP까지 방해할 수 있습니다. 단계별 구현 방식은 완벽한 Core Web Vitals를 유지하며 채팅 위젯을 구현하는 방법에서 확인하세요.

  • 채팅 위젯은 주요 콘텐츠가 모두 로드된 후에 로드하세요. 인터넷 역사상 페이지의 핵심 콘텐츠가 뜨기도 전에 채팅부터 필요했던 사용자는 단 한 명도 없었습니다. 첫 렌더링이 완료된 후 requestIdleCallback을 이용하거나 방문자가 스크롤할 때 위젯을 초기화하세요.
  • 준비가 끝날 때까지 채팅 위젯을 숨겨 두세요. 위젯이 레이아웃 시프트를 유발한다면 완전히 렌더링될 때까지 opacity: 0으로 유지하여, 눈에 보이는 콘텐츠를 흔들지 않고 백그라운드에서 레이아웃을 완료하게 만드세요. 그런 다음 CSS transition으로 서서히 페이드인시키세요.
  • 가벼운 채팅 위젯을 꼼꼼히 비교해 선택하세요. 위젯 솔루션에 따라 Core Web Vitals에 미치는 영향이 천차만별입니다. 도입을 결정하기 전에 제공업체별 JavaScript 번들 크기, 네트워크 요청 수, INP에 미치는 영향을 반드시 비교 분석하세요.

서비스 워커 성능 최적화

서비스 워커는 에셋은 물론 전체 페이지까지 캐싱하여 재방문 속도를 획기적으로 개선하며, 재방문 사용자의 TTFB를 단축합니다. 하지만 잘못 만들면 오히려 페이지 이동 속도를 떨어뜨립니다. 자세한 내용은 캐시 유효 기간 최적화 가이드를 확인하세요.

  • 핵심 에셋은 서비스 워커에 캐싱하세요. 정적 에셋(CSS, JavaScript, 폰트, 이미지)에 캐시 우선 전략을 적용하면 재방문자가 로컬 캐시에서 사이트를 거의 즉각적으로 로드할 수 있습니다. 가장 중요한 리소스는 install 이벤트 단계에서 미리 캐싱하세요.
  • 서비스 워커 코드를 간결하게 유지하세요. 복잡한 라우팅 로직, event.waitUntil()의 과도한 사용, 방대한 precache 매니페스트는 모두 설치 과정을 지연시킵니다. 자주 바뀌지만 변경 즉시 최신 상태여야 할 필요는 없는 리소스라면 stale-while-revalidate 전략을 사용하세요.

동영상 콘텐츠 최적화

동영상이 viewport에서 가장 크게 보이는 콘텐츠라면 LCP 요소가 되며, 파일이 크면 다른 중요 리소스의 대역폭까지 빼앗아 갑니다.

  • 합리적인 화질 설정과 최신 코덱(H.264, VP9, AV1)으로 동영상을 압축하세요. 화면에 실제 표시되는 크기에 맞춰 인코딩해야 합니다. 400px 너비로 표시되는 동영상을 1920px로 만들 필요는 없습니다. 2-pass 인코딩을 적용하면 동일 파일 크기 대비 최상의 화질을 얻을 수 있습니다.
  • 첫 화면 아래에 있는 동영상은 lazy load하세요. <iframe>에 loading="lazy"를 지정하거나 Intersection Observer API로 동영상 로드를 지연시키세요. 자동 재생되는 배경 동영상은 poster 이미지로 대체하고, 방문자가 근처로 스크롤할 때만 동영상을 로드하세요.
  • 동영상은 빠른 CDN에 호스팅하세요. 동영상 파일은 용량이 크기 때문에 CDN 배포의 효과를 크게 누릴 수 있습니다. 동영상 전용 CDN이나 호스팅 서비스(Cloudflare Stream, Mux, Bunny.net)를 이용하면 적응형 비트레이트 스트리밍, 글로벌 분산 전송, 최적화된 전송 기능까지 함께 활용할 수 있습니다.
  • <video> 요소에는 항상 poster 속성을 설정하세요. 동영상이 로드되는 동안 브라우저가 포스터를 즉시 렌더링할 수 있습니다. 포스터가 LCP 요소가 될 수도 있으므로 다른 LCP 이미지와 마찬가지로 최적화해야 합니다.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Search Console에서 경고 받으셨어요?

50페이지짜리 PDF 말고, 필드 데이터로 뒷받침된 우선순위 수정 목록을 드립니다.

감사 요청
Core Web Vitals 완벽 체크리스트 (2026) Core Web Vitals Core Web Vitals 완벽 체크리스트 (2026)