← 글 목록

notes

웹지표 성능 확인( 웹 바이탈 ) / 개선 ( LCP , FID , CLS , INP )

LCP·FID·CLS·INP 등 웹 바이탈 측정과 성능 개선 포인트 정리.

  • 지금 Core Web Vitals는 LCP(로딩), INP(반응성), CLS(안정성) 세 가지다. FID는 2024년 3월에 INP로 바뀌었다.
  • 기준은 LCP 2.5초, INP 200ms, CLS 0.1 이하가 좋음이고, 실제 사용자 75% 지점으로 판정한다.
  • 랩 도구(Lighthouse)로 원인을 찾고, 점수는 필드 데이터(CrUX, Search Console)로 확인한다.

처음엔 지표 이름만 적어 두고 FID 설명에 FCP 내용을 잘못 적기도 했었다. 지금은 현재 기준인 INP를 중심으로 보고, FID는 예전 리포트를 읽을 때를 위해 남겨 둔다.

Core Web Vitals이 뭔가

구글이 “사용자 경험의 핵심”으로 보는 성능 지표 묶음이다. SEO 순위 요인에도 들어가고, PageSpeed Insights / CrUX / Search Console에서 숫자로 보인다. 전부는 아니지만 로딩·상호작용·시각 안정성을 빠르게 가늠할 때 쓴다.

LCP — Largest Contentful Paint

뷰포트 안에서 가장 큰 콘텐츠(큰 이미지, 히어로, 큰 텍스트 블록)가 그려지는 시점. “페이지 전부 로드”가 아니라 “눈에 띄는 메인 조각” 기준.

줄이는 쪽: 히어로 이미지 최적화·우선 로딩, 서버/TTFB, 렌더 막는 CSS/JS, 웹폰트 지연.

FID — First Input Delay

사용자가 첫 클릭·탭·키 입력을 한 뒤, 브라우저가 그 이벤트를 처리하기 시작할 때까지의 지연. “핸들러 실행 시간”이 아니라 입력 대기열에 막힌 시간.

이름이 비슷한 FCP(First Contentful Paint)와 헷갈리기 쉽다. FCP는 첫 콘텐츠가 그려지는 시점이라 로딩 쪽 지표고, Core Web Vitals 세 가지에는 들어가지 않는다. FID는 2024년 3월에 Core Web Vitals에서 빠지고 INP가 그 자리를 대신했다.

긴 메인 스레드 작업(큰 JS 파싱/실행)이 FID를 망가뜨린다. 코드 스플리팅, 불필요 hydration 줄이기.

INP — Interaction to Next Paint

클릭·키보드 등 상호작용 후 다음 페인트까지. 페이지에 머문 동안 일어난 상호작용 중 거의 가장 느린 것이 그 페이지의 INP가 된다. 그래서 첫 클릭만 빨라도 안 되고, 필터나 탭처럼 자주 누르는 버튼이 다 가벼워야 한다.

무거운 클릭 핸들러, 동기 레이아웃 thrashing, 큰 리스트 렌더가 숫자를 올린다. Web Worker로 계산을 빼는 패턴도 여기랑 맞닿는다.

CLS — Cumulative Layout Shift

로드 중에 요소가 얼마나 밀려 움직였는지. 이미지·광고·웹폰트 영역에 크기 예약이 없으면 버튼이 갑자기 아래로 밀린다.

width/height 또는 aspect-ratio, 스켈레톤, 폰트 size-adjust/font-display로 줄인다.

어디서 재나

  • PageSpeed Insights — 랩 + 필드(있으면)
  • Chrome UX Report (CrUX) — 실제 사용자
  • Lighthouse — 랩 진단·개선 제안
  • Search Console — URL 그룹별 CWV

로컬에서만 예쁘고 필드가 나쁜 경우가 많아서, 배포 후 CrUX/PSI 필드도 본다. Lighthouse는 사람이 클릭하지 않으니 INP를 못 재고, 대신 TBT(Total Blocking Time)를 보면 비슷한 감을 잡을 수 있다.

web-vitals 라이브러리로 직접 모으기

트래픽이 적은 페이지는 CrUX에 데이터가 아예 안 나오니, 직접 모아서 봤다. attribution 빌드를 쓰면 어떤 요소가 느렸는지까지 같이 온다.

import { onLCP, onINP, onCLS } from 'web-vitals/attribution';

function send(metric) {
  navigator.sendBeacon('/api/vitals', JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    page: location.pathname,
  }));
}

onLCP(send);
onINP(send);
onCLS(send);

Next.js라면 useReportWebVitals 훅으로 같은 값을 받을 수 있다.

프론트에서 손대는 순서 (내가 쓰는 감)

  • LCP 후보(히어로)부터 이미지·우선순위
  • 메인 번들 무게 → INP/FID
  • 레이아웃 예약 → CLS
  • 그다음 TTFB·캐시·CDN