
Core Web Vitals 실전 가이드 — LCP·INP·CLS를 데이터로 다루는 법
런칭 전 반드시 확인해야 할 웹·모바일 성능 바이탈(Core Web Vitals) 기준과, 측정 → 원인 귀속 → 최적화 → 재측정으로 이어지는 실전 기술 가이드. LCP·INP·CLS 정밀 해부 + Lab/RUM 측정 방법론 + 기본 최적화 기법.
요약
- 성능은 주관적 느낌이 아니라 측정 가능한 지표로 관리한다. 사용자 체감의 핵심은 Core Web Vitals(CWV) 세 가지 — LCP(로딩), INP(반응성), CLS(시각적 안정성).
- 판정 기준(Google 공식 Core Web Vitals): LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1 이면 "좋음". 현장 데이터는 P75(상위 75퍼센타일) 로 본다.
- 측정은 두 축이다. Lab(합성) 은 재현 가능·배포 전 진단용, RUM(실사용자) 은 진짜 체감·필드 대표성용. INP 실측은 RUM에서만 나오고, Lab에서는 TBT를 프록시로 쓴다.
- 최적화는 일회성이 아니라 측정 → 원인 귀속(attribution) → 개선 → 재측정의 지속 사이클이다.
- 아래 본문은 각 기법을 ① 문제 → ② 원리 → ③ 적용 → ④ 코드 → ⑤ 검증 틀로 정리한다.
1. 서론 — 성능은 왜 UX이자 비즈니스 문제인가
1.1 모바일이라는 제약이 큰 실행 환경
데스크톱 개발 환경에서 "빠르다"고 느낀 페이지가 실제 사용자의 모바일에서 느린 이유는, 두 환경의 물리적 조건이 다르기 때문이다.
- CPU: 중저가 모바일 기기의 단일 코어 성능은 개발용 데스크톱 대비 크게 낮다. JavaScript 파싱·실행·레이아웃 계산이 모두 메인 스레드에서 일어나므로, CPU가 느리면 같은 코드라도 몇 배 느리게 실행된다.
- 네트워크: 셀룰러 환경은 대역폭뿐 아니라 지연(latency)과 변동성이 크다. TTFB와 리소스 로드가 들쭉날쭉해진다.
- 단말 편차: 최신 플래그십부터 수년 전 보급형까지 스펙 분포가 넓다. "내 폰에선 빠른데"가 통하지 않는다.
비유: 성능 최적화는 가장 좋은 회선의 최신 폰이 아니라 붐비는 지하철에서 3년 된 보급폰을 쓰는 사용자를 기준으로 설계하는 일이다.
1.2 성능이 지표에 미치는 영향(일반론)
일반적으로 로딩이 느리고 반응이 굼뜬 페이지는 이탈률이 오르고 전환·재방문에 부정적 영향을 준다는 것이 업계의 공통된 관찰이다. 구체적 수치는 서비스·업종·트래픽 특성에 따라 크게 다르므로 자사 데이터로 확인해야 하며, 여기서는 "성능 개선이 UX·비즈니스 지표와 상관관계가 있다"는 원칙만 전제한다.
핵심 요약: 모바일의 느린 CPU·불안정 네트워크·넓은 단말 편차가 체감 성능을 좌우한다. 성능은 측정해 관리해야 할 UX·비즈니스 지표다.
2. Core Web Vitals 정밀 해부
Core Web Vitals(CWV)는 Google이 정의한 사용자 경험 지표로, 경험을 세 축 — 로딩(LCP) · 반응성(INP) · 시각적 안정성(CLS) — 으로 계량한다. 아래 판정 임계값은 Google이 공식 문서에서 공개한 Core Web Vitals 기준을 그대로 따른다(web.dev = Google이 운영하는 웹 개발 공식 문서 사이트, 및 Chrome 개발자 문서. 원문 링크는 문서 말미 「참고 자료」 참조).
2.1 지표 기준표 (Google 공식 Core Web Vitals 기준)
| 지표 | 측정 대상 | 좋음(Good) | 개선 필요 | 나쁨(Poor) |
|---|---|---|---|---|
| LCP | 최대 콘텐츠 렌더 시점(로딩) | ≤ 2.5s | 2.5s ~ 4.0s | > 4.0s |
| INP | 인터랙션 반응성 | ≤ 200ms | 200ms ~ 500ms | > 500ms |
| CLS | 누적 레이아웃 이동(안정성) | ≤ 0.1 | 0.1 ~ 0.25 | > 0.25 |
필드(현장) 데이터는 방문자 분포의 P75(상위 75퍼센타일) 값으로 판정한다. "대부분의 사용자가 겪는 최악에 가까운 경험"을 기준선으로 삼기 위함이다.
2.2 LCP (Largest Contentful Paint)
정의. 뷰포트에 보이는 요소 중 가장 큰 콘텐츠 요소가 렌더링된 시점. 보통 히어로 이미지, 큰 배경 이미지, 큰 텍스트 블록(H1·문단)이 LCP 요소가 된다.
무엇이 LCP 요소인가. <img>, <image>(SVG 내), background-image가 있는 블록, <video>의 포스터, 큰 텍스트 노드 등. 렌더 과정에서 더 큰 요소가 나타나면 LCP 후보가 갱신된다.
흔한 지연 원인(4대 구간).
- 느린 서버 응답(TTFB) — 문서 자체가 늦게 도착.
- 렌더 차단 리소스 — <head>의 동기 CSS/JS가 렌더를 막음.
- 리소스 로드 지연 — LCP 이미지가 늦게 발견되거나 우선순위가 낮음.
- 클라이언트 렌더링(CSR) — 빈 HTML을 받은 뒤 JS로 그리면, JS 실행이 끝나야 LCP가 찍힘.
진단 포인트. LCP를 위 네 구간(TTFB → 리소스 로드 지연 → 로드 시간 → 렌더 지연)으로 쪼개어 어느 구간이 큰지 본다. CSR 기반 페이지는 LCP가 구조적으로 불리하다.
2.3 INP (Interaction to Next Paint)
정의. 사용자의 인터랙션(클릭·탭·키 입력)에 대해 다음 프레임이 그려지기까지의 지연. 페이지 수명 동안 발생한 인터랙션들 중 대표값(대체로 최악에 가까운 값)을 보고한다. INP는 다음 세 구간의 합으로 구성된다.
- 입력 지연(Input delay) — 이벤트가 처리되기 전, 메인 스레드가 다른 작업에 점유된 시간.
- 처리 시간(Processing time) — 이벤트 핸들러가 실제로 실행되는 시간.
- 프레젠테이션 지연(Presentation delay) — 핸들러 후 브라우저가 다음 프레임을 그리기까지의 시간.
왜 FID를 대체했나. 과거 지표 FID(First Input Delay)는 첫 입력의 지연만 측정해 "입력 지연" 한 조각만 봤다. INP는 모든 인터랙션의 전체 지연(처리+프레젠테이션 포함) 을 보므로 실제 반응성을 훨씬 정확히 반영한다. INP는 2024년 3월 CWV의 반응성 지표로 정식 채택되어 FID를 대체했다.
악화 원인. 긴 작업(Long Task)으로 메인 스레드가 점유됨, 과도하거나 무거운 이벤트 핸들러, 큰 DOM 갱신, 서드파티 스크립트의 메인 스레드 경쟁.
2.4 CLS (Cumulative Layout Shift)
정의. 로딩·인터랙션 중 예기치 않게 요소가 밀리는 양의 누적치(이동 영향 × 이동 거리). 사용자가 버튼을 누르려는 순간 레이아웃이 밀려 오터치가 나는 경험이 CLS로 계량된다.
흔한 원인.
- 크기 미지정 이미지/미디어 — 로드 전 공간이 예약되지 않아 로드 시 밀림.
- 동적 삽입 콘텐츠 — 배너·알림·광고가 기존 콘텐츠 위에 끼어듦.
- 웹폰트 FOIT/FOUT — 폰트 교체 시 글자 크기·자간이 바뀌며 재배치.
- 광고·임베드 — 크기가 가변적인 서드파티 삽입물.
2.5 보조 지표 — FCP·TTFB·TBT·Speed Index
| 지표 | 의미 | CWV와의 관계 |
|---|---|---|
| TTFB (Time To First Byte) | 첫 바이트 도착까지 | LCP의 하위 구간. 서버·네트워크 병목 진단 |
| FCP (First Contentful Paint) | 첫 콘텐츠 렌더 | LCP의 선행 지표. 렌더 차단 진단 |
| TBT (Total Blocking Time) | 로드 중 메인 스레드가 막힌 총 시간 | Lab에서 INP/반응성의 프록시 |
| Speed Index | 화면이 시각적으로 채워지는 속도 | 로딩 체감 보조 지표 |
핵심 요약: CWV는 로딩(LCP)·반응성(INP)·안정성(CLS) 세 축이며 기준은 2.5s / 200ms / 0.1. 보조 지표(TTFB·FCP·TBT·SI)는 각 CWV의 원인 구간을 파고들 때 쓴다.
3. Lab(합성) vs RUM(실사용자) — 측정의 두 축
성능 데이터에는 성격이 다른 두 종류가 있고, 둘 중 하나만 보면 반드시 오판한다.
3.1 Lab(합성, Synthetic)
통제된 환경에서 자동화 도구가 페이지를 로드해 측정한다.
- 장점: 재현성(같은 조건 반복), 배포 전 측정 가능, 상세한 원인 분석(트레이스) 가능.
- 한계: 실제 사용자의 다양한 기기·네트워크·인터랙션을 담지 못한다. 특히 실제 인터랙션이 없으므로 INP를 측정할 수 없다.
3.2 RUM(실사용자, Real User Monitoring)
실제 방문자의 브라우저에서 성능 데이터를 수집한다.
- 장점: 진짜 체감 성능, 필드 대표성(실제 기기·회선 분포).
- 한계: 배포 후에만 데이터가 쌓이고, 환경 편차가 커 개별 원인 규명이 어렵다.
3.3 핵심 원리 — INP는 왜 Lab에서 안 나오나
INP는 실제 인터랙션이 있어야 측정된다. Lab은 자동 로드만 하고 사람의 클릭·입력이 없으므로 INP를 직접 측정하지 못한다. 그래서:
- Lab에서는 TBT(Total Blocking Time) 를 반응성의 프록시로 사용한다. TBT가 크면 인터랙션도 느릴 가능성이 높다.
- 실측 INP는 RUM/필드 데이터에서만 얻는다.
3.4 75번째 백분위수(P75) — 공식 평가 방법
Google은 Core Web Vitals를 페이지 방문 분포의 75번째 백분위수(75th percentile, 통칭 P75) 값에서 평가한다. 이는 web.dev·Chrome 개발자 문서에 명시된 공식 평가 방법이며, "합격/불합격" 판정도 이 P75 값 기준이다. 평균은 소수의 빠른 세션에 가려 문제를 감추지만, 75번째 백분위수는 "방문의 약 3/4가 이 값보다 좋게 경험한다"는 보수적 기준선을 제공한다.
핵심 요약: Lab은 배포 전 재현·원인분석, RUM은 배포 후 실체감. INP 실측은 RUM에서만 나오고 Lab은 TBT로 대신 본다. 필드는 P75로 판정한다.
4. 측정 방법론 — 신뢰할 수 있는 수치를 얻는 법
수치는 어떻게 측정했느냐에 따라 크게 달라진다. 방법론이 흔들리면 개선 여부를 판단할 수 없다.
4.1 스로틀링 — 조건을 사용자에게 맞춘다
- CPU 슬로우다운: 개발 기기의 CPU를 인위적으로 느리게(예: 4×/6× 슬로우다운) 설정해 보급형 모바일에 근접시킨다.
- 네트워크 조건: 대역폭·지연을 제한(예: 느린 4G 프로파일)해 실제 회선에 근접시킨다.
- 주의: 스로틀링 설정과 실행 기기에 따라 Lab 값은 크게 달라진다. 따라서 Lab 수치는 "절대값"이 아니라 동일 조건에서의 상대 비교(개선 전/후) 로 해석해야 한다.
4.2 First View vs Repeat View, 그리고 중앙값
- First View(첫 방문) 와 Repeat View(재방문) 는 캐시 영향으로 값이 다르다. 목적에 맞는 것을 본다(신규 유입 최적화면 First View).
- 단일 측정은 노이즈가 크다. 여러 회 측정 후 중앙값(median) 을 사용한다. 한 번의 좋은/나쁜 값에 속지 않기 위함이다.
4.3 원인 귀속(attribution) — DevTools Performance
병목을 "느낌"으로 찾지 말고 트레이스로 귀속한다. Chrome DevTools의 Performance 패널이 표준 도구다.
- Bottom-Up vs Call Tree
- Bottom-Up(self time): 각 함수가 자기 자신에서 실제로 소비한 CPU 시간. "실제로 무거운 함수"를 찾는다.
- Call Tree(total time): 자식 호출을 포함한 총 시간. "어느 호출 경로가 무거운가"를 본다.
- 실무 순서: Bottom-Up으로 self time이 큰 항목을 찾고 → Call Tree로 호출 맥락을 확인한다.
- Group by 로 병목 주체 좁히기: URL / 서드파티 주체 / 작업 종류(스크립팅·렌더·페인트) / 카테고리 로 묶어, "어느 도메인·어느 스크립트·어떤 종류의 작업"이 시간을 먹는지 단계적으로 좁힌다.
- 최신 어트리뷰션 기법: Long Animation Frames API(LoAF) 와 web-vitals 라이브러리(v4대)의 INP 어트리뷰션 을 쓰면 "어떤 스크립트/어떤 인터랙션이 느린 프레임을 유발했는가"를 식별할 수 있다. 단, 브라우저·API 지원 범위와 사용법은 시점에 따라 바뀌므로 최신 공식 문서 확인을 권장한다.
핵심 요약: 스로틀링·First/Repeat·중앙값으로 조건을 통제하고, DevTools의 Bottom-Up/Call Tree + Group by로 병목을 귀속한다. INP 원인은 LoAF·web-vitals 어트리뷰션으로 좁힌다(지원 범위는 공식 문서 확인).
5. 기본 최적화 기법
각 기법을 ① 문제 → ② 원리 → ③ 적용 → ④ 코드(안티패턴→개선) → ⑤ 검증 틀로 정리한다.
5.1 렌더링 지연 — content-visibility
① 문제. 긴 페이지에서 화면 밖(스크롤해야 보이는) 콘텐츠까지 초기 렌더에 포함되어 레이아웃·페인트 비용이 커진다.
② 원리. content-visibility: auto 는 화면 밖 요소의 렌더링(레이아웃·페인트)을 필요할 때까지 건너뛴다. 단, 렌더를 스킵하면 크기를 모르게 되어 스크롤바가 튀므로 contain-intrinsic-size 로 예상 크기를 알려줘야 한다.
③ 적용. 세로로 긴, 독립적인 섹션(카드 목록, 아티클 블록)에 적용. 주의: display:none 으로 이미 숨긴 요소엔 의미가 없고(렌더 대상이 아님), 앵커형 display:block 섹션에서 효과가 있다.
④ 코드
/* 개선: 화면 밖 섹션 렌더 스킵 + 크기 예약 */
.section {
content-visibility: auto;
contain-intrinsic-size: auto 600px; /* 예상 높이 — 스크롤 점프 방지 */
}
⑤ 검증. DevTools Performance에서 초기 렌더의 Layout/Paint 시간 감소를 확인. 스크롤 시 레이아웃 점프(CLS)가 생기지 않는지 함께 본다.
5.2 강제 리플로우(레이아웃 스래싱) 회피
① 문제. 루프 안에서 읽기(측정)와 쓰기(스타일 변경)를 번갈아 하면, 브라우저가 매번 레이아웃을 다시 계산(강제 리플로우)해 메인 스레드를 잡아먹는다. ② 원리. 레이아웃 값을 읽는 순간 브라우저는 최신 레이아웃을 즉시 계산해야 한다. 읽기→쓰기→읽기가 섞이면 계산이 반복된다. 읽기를 먼저 몰아서 하고, 쓰기를 나중에 몰아서 하면 리플로우가 한 번으로 줄어든다. ③ 적용. 측정값은 캐싱하고, DOM 쓰기는 배칭한다.
④ 코드
// 안티패턴: 읽기와 쓰기가 교차 → 매 반복 강제 리플로우
items.forEach((el) => {
const w = el.getBoundingClientRect().width; // 읽기
el.style.width = w * 1.1 + 'px'; // 쓰기 → 다음 읽기에서 리플로우
});
// 개선: 읽기 배칭 → 쓰기 배칭
const widths = items.map((el) => el.getBoundingClientRect().width); // 읽기 몰아서
items.forEach((el, i) => { el.style.width = widths[i] * 1.1 + 'px'; }); // 쓰기 몰아서
⑤ 검증. Performance 트레이스에서 "Recalculate Style / Layout"이 반복적으로 찍히는 보라색 구간이 줄었는지 확인.
5.3 긴 작업(Long Task, >50ms) 분할
① 문제. 50ms를 넘는 작업은 그 동안 메인 스레드를 독점해 인터랙션을 막는다(입력 지연 → INP 악화). ② 원리. 큰 작업을 잘게 쪼개 중간중간 메인 스레드를 양보(yield) 하면, 사이사이 사용자 입력이 처리될 틈이 생긴다. ③ 적용. 대량 반복·직렬 계산을 청킹하고, 급하지 않은 작업은 유휴 시간에 미룬다.
④ 코드
// 개선: 중간에 메인 스레드 양보 (scheduler.yield 지원 시)
async function processAll(items) {
for (let i = 0; i < items.length; i++) {
doHeavyWork(items[i]);
// 일정 주기마다 양보 → 입력 처리 틈을 확보
if (i % 50 === 0 && 'scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
}
}
}
// 급하지 않은 작업은 유휴 시간에
requestIdleCallback(() => precomputeCache());
scheduler.yield() 는 비교적 최신 API로 브라우저 지원 범위가 제한적일 수 있으니 폴백(예: setTimeout(0)/await new Promise(r=>setTimeout(r)))과 함께 쓰고, 최신 지원 현황은 공식 문서를 확인한다.
⑤ 검증. Performance에서 50ms를 넘는 Long Task(빨간 삼각형)가 줄었는지, 필드 INP(RUM)가 개선되는지 확인.
5.4 이벤트·스크롤 성능
① 문제. 스크롤·터치·resize 같은 고빈도 이벤트에서 무거운 핸들러가 매 프레임 실행되면 스크롤이 끊긴다.
② 원리. passive 리스너로 스크롤 블로킹을 없애고, 시각 갱신은 requestAnimationFrame 에 맞춘다. 고빈도 입력은 debounce/throttle 로 실행 빈도를 낮춘다.
④ 코드
// 개선: passive + rAF로 스크롤 핸들러 최적화
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => { updateOnScroll(); ticking = false; });
ticking = true;
}
}, { passive: true });
⑤ 검증. 스크롤 중 Performance 트레이스에서 프레임 드롭(긴 프레임)이 줄고 60fps에 근접하는지 확인.
5.5 이미지 최적화
① 문제. 이미지는 대개 페이지에서 가장 무거운 리소스이자 LCP 요소다. 무겁거나 늦게 발견되면 LCP가 나빠지고, 크기 미지정이면 CLS를 유발한다. ② 원리. ⑴ 포맷을 가볍게(WebP/AVIF), ⑵ 화면 크기에 맞는 해상도만 전달(반응형), ⑶ 화면 밖 이미지는 지연 로드, ⑷ LCP 이미지는 일찍·높은 우선순위로 로드. ③ 적용.
④ 코드
<!-- 안티패턴: 크기 미지정 + 원본 그대로 → CLS + 과대 전송 --> <img src="/hero.png" /> <!-- 개선: 크기 지정(CLS 방지) + 반응형 + 지연/우선순위 --> <img src="/hero-800.avif" srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1600.avif 1600w" sizes="(max-width: 768px) 100vw, 800px" width="800" height="450" loading="lazy" decoding="async" alt="설명" /> <!-- LCP 이미지는 지연 로드하지 말고 preload + 높은 우선순위 --> <link rel="preload" as="image" href="/hero-800.avif" fetchpriority="high" />
주의: LCP 이미지에는 loading="lazy" 를 쓰지 않는다(발견·로드가 늦어져 오히려 LCP 악화). lazy는 화면 밖 이미지에만.
⑤ 검증. LCP 요소가 이미지면 그 로드 시점이 앞당겨졌는지, 이미지 크기 지정으로 CLS가 사라졌는지 확인.
5.6 폰트 최적화
① 문제. 웹폰트 로드 중 텍스트가 안 보이거나(FOIT), 시스템 폰트로 보였다가 교체되며(FOUT) 레이아웃이 밀려 CLS·FCP가 나빠진다.
② 원리. font-display 로 교체 정책을 정하고, 폰트를 preload 로 일찍 받으며, 사용 글자만 서브셋으로 줄여 용량을 낮춘다.
④ 코드
@font-face {
font-family: 'Brand';
src: url('/fonts/brand-subset.woff2') format('woff2');
font-display: swap; /* 텍스트 즉시 표시 후 교체 (FOIT 방지) */
font-weight: 400 700; /* 가변폰트면 범위 지정 */
}
<link rel="preload" as="font" type="font/woff2" href="/fonts/brand-subset.woff2" crossorigin />
교체 시 CLS를 더 줄이려면 폴백 폰트의 메트릭을 맞추는 기법(예: size-adjust/ascent-override 등)을 함께 검토한다. 세부 지원은 공식 문서 확인 권장.
⑤ 검증. 폰트 교체 시점의 레이아웃 이동(CLS)과 텍스트 표시 시점(FCP)을 확인.
5.7 리소스 전달 — 코드 스플리팅·크리티컬 CSS·프리커넥트
① 문제. 초기 로드에 불필요한 JS/CSS까지 함께 받으면 렌더가 늦어진다(렌더 차단).
② 원리. ⑴ 초기 화면에 필요한 코드만 나누어 로드(코드 스플리팅), ⑵ 첫 화면에 필요한 CSS만 우선(크리티컬 CSS), ⑶ 곧 연결할 도메인은 미리 커넥트(preconnect), 중요한 리소스는 preload, ⑷ 렌더 차단 리소스 제거.
④ 코드
<!-- 곧 쓸 서드파티 도메인 연결 준비 --> <link rel="preconnect" href="https://cdn.example.com" crossorigin />
// 개선: 필요할 때만 무거운 모듈 로드(코드 스플리팅)
button.addEventListener('click', async () => {
const { openEditor } = await import('./editor.js'); // 초기 번들에서 분리
openEditor();
});
⑤ 검증. 초기 번들 크기·렌더 차단 리소스 수 감소, FCP/LCP 개선을 확인.
5.8 서드파티 스크립트 거버넌스 (분석·마케팅 태그)
① 문제. 분석·광고·챗봇 등 서드파티 태그는 대부분 메인 스레드에서 실행된다. 태그가 늘수록 우리 코드와 메인 스레드를 놓고 경쟁해 입력 지연 → INP가 악화된다. 또한 렌더 차단·추가 커넥션으로 LCP에도 영향을 준다.
② 원리. ⑴ 로딩을 비차단으로(async/defer), ⑵ 즉시 필요 없는 태그는 조건부·지연 로딩(가시성·상호작용 시점에), ⑶ 태그 매니저의 트리거를 최소화, ⑷ 가능하면 서버사이드 태깅으로 클라이언트 부담 이전, ⑸ 무거운 서드파티 JS는 웹워커로 오프로드(Partytown 등)해 메인 스레드에서 뺀다.
③ 적용. "이 태그가 초기 화면·첫 인터랙션에 꼭 필요한가?"를 기준으로 로드 시점을 미룬다.
④ 코드
<!-- 안티패턴: 동기 로드 → 렌더·메인스레드 차단 --> <script src="https://third-party.example.com/tag.js"></script> <!-- 개선: 비차단 로드 --> <script src="https://third-party.example.com/tag.js" async></script>
// 개선: 상호작용/유휴 시점까지 지연 로드
const load = () => import('./analytics.js');
if ('requestIdleCallback' in window) requestIdleCallback(load);
else window.addEventListener('load', load);
Partytown·서버사이드 태깅 등은 구성·호환성 고려가 필요하며 모든 태그에 적용되지는 않는다. 도입 전 대상 태그의 동작·지원을 검증한다. ⑤ 검증. DevTools Performance의 Group by 서드파티 주체로 각 태그의 메인 스레드 점유를 측정하고, 개선 후 TBT(Lab)·INP(RUM) 변화를 확인.
5.9 최적화 기법 요약표
| 목표 지표 | 기법 | 핵심 한 줄 |
|---|---|---|
| LCP | LCP 이미지 preload + fetchpriority | 가장 큰 요소를 일찍·우선 로드 |
| LCP/FCP | 렌더 차단 제거·크리티컬 CSS·코드 스플리팅 | 초기 경로를 가볍게 |
| INP | Long Task 분할·yield, 서드파티 거버넌스 | 메인 스레드를 비워 입력을 받는다 |
| INP | 리플로우 회피(읽기/쓰기 배칭) | 불필요한 레이아웃 재계산 제거 |
| CLS | 이미지·미디어 크기 지정, 공간 예약 | 밀림을 원천 차단 |
| CLS | font-display·폰트 preload·서브셋 | 폰트 교체로 인한 재배치 최소화 |
| 렌더 비용 | content-visibility + contain-intrinsic-size | 화면 밖 렌더 스킵 |
핵심 요약: 기법은 결국 "① 초기 경로를 가볍게(LCP) ② 메인 스레드를 비워 입력을 받게(INP) ③ 공간을 미리 예약해 안 밀리게(CLS)"로 수렴한다.
6. 페이지 작성 후 성능 점검 워크플로우
6.1 권장 절차 (4단계)
- 개괄 확인 — PageSpeed Insights: 한 URL에 대해 Lab 점수 + 필드 데이터를 함께 개괄한다.
- 필드 확인 — CrUX/RUM: 실제 사용자 기준 CWV, 특히 Lab에서 안 나오는 INP를 P75로 확인한다.
- 원인 귀속 — DevTools Performance: 문제 지표의 병목을 Bottom-Up/Call Tree + Group by로 귀속한다.
- 개선 후 재측정: 가설을 하나씩 적용하고 동일 조건에서 재측정해 인과를 확인한다.
6.2 도구 비교표 (공개 도구)
| 도구 | 성격 | 주 용도 |
|---|---|---|
| PageSpeed Insights | Lab + 필드 요약 | 배포 URL의 상태 개괄 |
| Chrome DevTools Performance | Lab / 원인 분석 | 병목 트레이스·원인 귀속 |
| WebPageTest | Lab(다양한 조건) | 조건별 상세 측정·비교 |
| CrUX (Chrome UX Report) | RUM(필드) | 실사용자 CWV(P75) |
| web-vitals 라이브러리 | RUM(자체 수집) | 자사 페이지에서 CWV·어트리뷰션 수집 |
6.3 판정 원칙
- 판정은 Google 공식 CWV 기준(web.dev·Chrome 문서) (2.5s / 200ms / 0.1, P75)을 사용한다.
- Lab 단일 점수에 집착하지 말 것. Lab 점수는 조건에 민감하고 INP를 담지 못한다. 반드시 필드 데이터와 병행해 "실사용자가 실제로 개선되었는가"로 최종 판정한다.
핵심 요약: PSI(개괄) → 필드(INP 포함) → DevTools(귀속) → 재측정. Lab 점수 하나가 아니라 필드 데이터로 판정한다.
7. 결론 — 성능은 상태가 아니라 사이클이다
성능 최적화는 "한 번 끝내는 작업"이 아니라 측정 → 원인 귀속 → 개선 → 재측정 을 반복하는 지속적 프로세스다. 코드·콘텐츠·서드파티 태그는 시간이 지나며 계속 늘고 변하므로, 한 번 좋았던 지표도 관리하지 않으면 퇴화한다.
핵심 태도는 세 가지다. ⑴ 느낌이 아니라 데이터로 말한다(Lab+필드 병행). ⑵ 가장 불리한 사용자(느린 기기·회선)를 기준으로 설계한다. ⑶ 개선은 한 번에 하나씩, 재측정으로 인과를 확인한다.
실행 체크리스트 (런칭 전)
- LCP 요소를 식별하고, LCP 이미지는
preload + fetchpriority="high"(lazy 금지). - 모든 이미지·미디어에
width/height(또는 aspect-ratio) 지정 → CLS 방지. - 웹폰트
font-display: swap+ preload + 서브셋 적용. - 초기 렌더 차단 JS/CSS 제거, 코드 스플리팅·크리티컬 CSS 적용.
- Long Task(>50ms) 확인·분할, 고빈도 이벤트
passive+rAF+throttle. - 서드파티 태그 목록화 → 비차단/지연/조건부 로딩, 불필요 태그 제거.
content-visibility로 긴 페이지의 화면 밖 렌더 비용 절감(display:none 요소 제외).- PSI로 개괄 → 필드(CrUX/RUM)에서 INP 포함 P75 확인 → DevTools로 귀속 → 재측정.
- 판정은 Google 공식 기준(2.5s / 200ms / 0.1, P75)으로, Lab+필드 병행.
참고 자료 (공식·공개 문서)
본문의 모든 임계값·정의는 아래 ① Core Web Vitals 공식 기준(Google) 을 1차 원출처로 삼는다. web.dev는 Google이 운영하는 웹 개발 공식 문서 사이트이며, developer.chrome.com은 Chrome 개발자 공식 문서다. 아래 링크는 작성 시점 접근 가능(200 응답) 확인.
① Core Web Vitals 공식 기준 — Google (1차 원출처)
Core Web Vitals 개요·임계값web.dev/articles/vitalsLCP(정의·기준)web.dev/articles/lcpINP(정의·기준)web.dev/articles/inpCLS(정의·기준)web.dev/articles/clsTTFBweb.dev/articles/ttfb② 최적화 가이드 — Google
LCP 최적화web.dev/articles/optimize-lcpINP 최적화web.dev/articles/optimize-inpLong Animation Frames(LoAF)developer.chrome.com/docs/web-platform/long-animation-frames③ 측정·진단 도구
PageSpeed Insights(Lab+필드)pagespeed.web.dev/Chrome DevTools Performance(Lab·원인 분석)developer.chrome.com/docs/devtools/performanceChrome UX Report / CrUX(필드 데이터)developer.chrome.com/docs/cruxweb-vitals 라이브러리(필드 CWV·어트리뷰션 수집, Google Chrome)github.com/GoogleChrome/web-vitalsWebPageTest(Lab 측정, 서드파티 공개 도구)www.webpagetest.org/도구의 UI·API·브라우저 지원 범위는 시점에 따라 바뀌므로, 세부 사용법·지원 현황은 각 최신 공식 문서를 확인한다. 본문에서 수치를 예로 든 스로틀링 배율(예: 4×/6×)·네트워크 프로파일 등은 환경 예시이며 절대 기준이 아니다.