Frontend Development

Core Web Vitals: LCP, INP, CLS 개념과 개선 방법

Kun Woo Kim 2025. 8. 20. 17:48
728x90

의의와 배경

Core Web Vitals는 구글이 웹 사용자 경험을 정량화하기 위해 정의한 핵심 성능 지표다. 이 지표는 실제 사용자 경험을 직접적으로 반영하며 검색 순위(SEO)에도 영향을 미친다. 프론트엔드 개발자는 필수적으로 이해하고 지속적으로 관리해야 한다.


목차

  • 개요: 지표 정의와 목표 기준
  • LCP: 의미, 원인, 개선 체크리스트
  • INP: 의미, 원인, 개선 체크리스트
  • CLS: 의미, 원인, 개선 체크리스트
  • 측정 방법: Lab vs Field, 실측 코드 예시
  • 실무 팁/흔한 실수
  • 체크리스트와 결론

개요

Core Web Vitals는 세 가지 지표로 구성된다.

지표 의미 권장 기준 주의 구간
LCP (Largest Contentful Paint) 가장 큰 콘텐츠가 화면에 보이기까지의 시간 ≤ 2.5s 2.5s ~ 4.0s
INP (Interaction to Next Paint) 사용자 상호작용 후 다음 페인트까지의 지연 ≤ 200ms 200ms ~ 500ms
CLS (Cumulative Layout Shift) 예기치 않은 레이아웃 이동의 누적량 ≤ 0.1 0.1 ~ 0.25

LCP: Largest Contentful Paint

  • 정의: 최초 뷰포트 내 가장 큰 텍스트·이미지·포스터 이미지가 렌더링되기까지의 시간
  • 악화 요인: 높은 TTFB, 대용량 히어로 이미지, 차단적인 CSS/JS, 비효율적인 폰트 로드

개선 체크리스트

  • 서버/네트워크: CDN 사용, 정적 캐시, 압축(gzip/br) 적용, HTTP/2·3 활성화, 초기 HTML의 TTFB를 낮춘다.
  • 리소스 우선순위: 핵심 CSS 인라인, 비핵심 CSS 지연, 크리티컬 이미지 preload를 적용한다.
  • 이미지 최적화: 포맷(AVIF/WebP)과 크기를 최적화하고 srcset/sizes를 사용한다. 히어로 이미지는 지연 로드를 피한다.
  • 폰트 전략: font-display: swap을 적용하고 필요한 글자만 서브셋 처리하며 폰트를 preload한다.
<link rel="preload" as="image" href="/hero.jpg" imagesrcset="/hero.jpg 1x, /hero@2x.jpg 2x" imagesizes="100vw">
<link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin>
/* FOIT 방지용 폰트 표시 전략 */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;
}

INP: Interaction to Next Paint

  • 정의: 페이지 체류 동안 발생한 상호작용(클릭·탭·키 입력)의 지연 중 최악값에 가까운 단일 값
  • 악화 요인: 메인 스레드 장기 점유(Long Task), 동기 JS, 무거운 이벤트 핸들러, 레이아웃 스래싱

개선 체크리스트

  • JS 예산 관리: 번들을 축소하고 코드 분할과 사용 시점 로딩(온디맨드)을 적용한다.
  • 이벤트 핸들러 최적화: 작업을 분할하고 우선순위가 낮은 작업은 setTimeout(0) 또는 requestIdleCallback로 위임한다.
  • 레이아웃 최적화: 읽기·쓰기 연산을 배치하고 스타일 계산을 최소화하며 애니메이션에는 transform/opacity를 우선 사용한다.
  • Long Task 감지: DevTools Performance로 50ms 초과 작업을 식별하여 분할한다.
// 긴 배열 처리 시 이벤트 반응성을 확보하기 위한 단순 작업 분할 예시
function processInChunks<T>(items: T[], chunkSize: number, handle: (chunk: T[]) => void) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    setTimeout(() => handle(chunk), 0);
  }
}

CLS: Cumulative Layout Shift

  • 정의: 예기치 못한 레이아웃 이동의 누적 정도
  • 악화 요인: 크기가 지정되지 않은 이미지·영상, 동적 콘텐츠 삽입, 광고·위젯, 지연된 웹폰트 로드로 인한 폰트 스왑

개선 체크리스트

  • 고정 공간 확보: 이미지·비디오에 width/height 또는 aspect-ratio를 명시한다.
  • 동적 요소 자리 확보: 광고·위젯 컨테이너 높이를 예약하고 콘텐츠 로딩 전 스켈레톤을 제공한다.
  • 폰트 로드 전략: font-display: swap을 적용하고 FOUT를 허용하며 폴백 폰트는 메트릭 유사 폰트를 사용한다.
  • 애니메이션 가이드: 레이아웃을 변경하는 top/left/width/height 대신 transform을 사용한다.
/* 레이아웃 이동 방지 */
img {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
  display: block;
}

측정 방법: 실험실(Lab) vs 실제(Field)

  • 실험실(Lab) 데이터: Lighthouse, DevTools(성능 탭)로 시뮬레이션 측정한다. 재현성이 높다.
  • 실제(Field) 데이터, RUM: 실제 사용자 환경에서 수집한다. 사용자 네트워크와 디바이스 특성을 반영한다.

실측 데이터 수집(React 예시)

npm i web-vitals
// vitals.ts
import { onCLS, onINP, onLCP, Metric } from 'web-vitals';

export function initWebVitals(report: (metric: Metric) => void) {
  onCLS(report);
  onINP(report);
  onLCP(report);
}
// App.tsx
import { useEffect } from 'react';
import { initWebVitals } from './vitals';

export default function App() {
  useEffect(() => {
    initWebVitals((metric) => {
      // 서버로 전송하여 대시보드로 집계한다.
      navigator.sendBeacon('/analytics', JSON.stringify({
        name: metric.name,
        value: metric.value,
        rating: metric.rating,
      }));
    });
  }, []);

  return (
    <div>...</div>
  );
}

실무 적용 팁

  • 이미지: 히어로 이미지는 preload와 적절한 포맷(AVIF/WebP)을 적용하고, 비핵심 이미지는 지연 로드한다.
  • 폰트: 핵심 폰트만 선택적으로 preload하고 font-display: swap을 적용한다. 폰트 종류와 가중치는 최소화한다.
  • 번들: 라우트 단위 코드 분할과 사용 시점 로딩을 적용하고, 타사 스크립트 예산을 관리한다.
  • 캐싱: 정적 에셋은 장기 캐시하고, HTML은 짧은 TTL과 재검증 정책을 사용한다.
  • 네트워크 힌트: preconnectdns-prefetch로 외부 리소스 초기 지연을 줄인다.
<link rel="preconnect" href="https://example-cdn.com" crossorigin>
<link rel="dns-prefetch" href="https://example-cdn.com">

흔한 실수

  • 이미지·비디오 크기를 지정하지 않아 CLS가 증가한다.
  • 초기 렌더에 불필요한 스크립트를 포함해 INP와 LCP가 악화된다.
  • 모든 폰트를 무분별하게 preload하여 네트워크 병목을 유발한다.
  • 크리티컬 CSS 없이 거대한 CSS를 한 번에 로드한다.

체크리스트

  • 히어로 이미지 포맷·크기를 최적화하고 preload를 적용했다.
  • 핵심 CSS는 인라인하고 비핵심 CSS는 지연 로드했다.
  • 폰트에 preloadfont-display: swap을 적용하고 서브셋을 구성했다.
  • 이미지·동영상에 width/height 또는 aspect-ratio를 지정했다.
  • 라우트 단위 코드 분할을 적용하고 타사 스크립트 예산을 수립했다.
  • RUM 수집(web-vitals)으로 실제 사용자 데이터를 확인했다.

결론: 개념 정리와 적용 가이드

  • 핵심: LCP(로딩), INP(응답성), CLS(시각 안정성)는 서로 다른 관점에서 동시에 관리해야 한다.
  • 적용 순서:
    1) TTFB와 히어로 리소스를 최적화하여 LCP를 개선한다.
    2) 코드 분할과 작업 분할로 INP를 개선한다.
    3) 크기 예약과 폰트 전략으로 CLS를 개선한다.
    4) RUM 수집으로 실제 사용자 기준을 확인하고 회귀를 방지한다.
728x90