Core Web Vitals 최적화
필드 데이터를 사용하여 사용자 영향을 식별하고 브라우저 트레이스를 통해 원인을 진단하여 세 가지 Core Web Vitals를 집중적으로 최적화합니다.
최적화 전에 측정하세요
실행 가능한 URL이 제공되면 성능 측정 워크플로우를 읽으세요. 다음 순서를 선호합니다:
- 페이지 수준 CrUX p75 데이터를 확인하고, 페이지 데이터를 사용할 수 없는 경우 출처(origin) 대체 데이터를 명확히 표시합니다.
- 명시된 조건에서 브라우저 성능 트레이스를 기록합니다. Chrome DevTools MCP를 사용하면 트레이스 요약에 관찰된 실험실 메트릭과 함께 CrUX를 포함할 수 있습니다.
- 실패하는 메트릭과 관련된 인사이트만 분석한 다음, 관련 코드와 리소스를 검사합니다.
- 수정 후 동등한 실험실 측정을 다시 실행합니다. 즉각적인 필드 개선을 주장하지 마세요. CrUX 및 자체 RUM에는 새로운 사용자 방문이 필요합니다.
소스 코드만 사용 가능한 경우, 잠재적 원인을 식별할 수는 있지만 런타임 증거 없이 LCP, INP 또는 CLS가 실패했다고 주장하지 마세요.
세 가지 메트릭
| 메트릭 | 측정 항목 | 우수 | 개선 필요 | 나쁨 |
|---|---|---|---|---|
| LCP | 로딩 | ≤ 2.5초 | 2.5초 – 4초 | > 4초 |
| INP | 상호작용 | ≤ 200ms | 200ms – 500ms | > 500ms |
| CLS | 시각적 안정성 | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |
Google은 75번째 백분위수로 측정합니다. 즉, 페이지 방문의 75%가 "우수" 임계값을 충족해야 합니다.
LCP: Largest Contentful Paint (최대 콘텐츠풀 페인트)
LCP는 가장 큰 가시 콘텐츠 요소가 렌더링되는 시점을 측정합니다. 일반적으로 다음 항목입니다:
- 히어로 이미지 또는 비디오
- 큰 텍스트 블록
- 배경 이미지
<svg>요소
일반적인 LCP 문제
1. 느린 서버 응답 (TTFB > 800ms)
수정: CDN, 캐싱, 최적화된 백엔드, 엣지 렌더링
2. 렌더링 차단 리소스
<!-- ❌ 렌더링 차단 -->
<link rel="stylesheet" href="/all-styles.css">
<!-- ✅ 중요 CSS는 인라인, 나머지는 지연 -->
<style>/* 중요 접경(above-fold) CSS */</style>
<link rel="preload" href="/styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
3. 느린 리소스 로드 시간
<!-- ❌ LCP 이미지가 스타일시트 로드 후에야 발견됨 -->
<div class="hero"></div>
<!-- ✅ 초기 HTML에서 발견 가능하고 우선순위가 지정됨 -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<img src="/hero.webp" alt="Hero" fetchpriority="high">
fetchpriority="high"가 포함된 발견 가능한 <img>를 선호하세요. 트레이스에서 리소스가 늦게 발견될 것으로 보이는 경우에만 preload를 추가하세요. 중복되거나 추측성 preload는 대역폭 경합을 유발할 수 있습니다.
4. 클라이언트 측 렌더링 지연
// ❌ JavaScript 이후에 콘텐츠 로드
useEffect(() => {
fetch('/api/hero-text').then(r => r.json()).then(setHeroText);
}, []);
// ✅ 서버 측 또는 정적 렌더링
// SSR, SSG 또는 스트리밍을 사용하여 콘텐츠가 포함된 HTML 전송
export async function getServerSideProps() {
const heroText = await fetchHeroText();
return { props: { heroText } };
}
5. Speculation Rules API로 탐색을 즉시 만들기
예측 가능한 동일 출처(same-origin) 여정이 있는 사이트의 경우, 다음 페이지를 사전 렌더링하면 성공적인 후속 탐색 속도를 크게 높일 수 있습니다. 이는 현재 페이지의 LCP를 수정하는 대체재가 아닌, 측정된 탐색 최적화로 취급하세요.
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}]
}
</script>
현재 Chrome 동작은 선택을 안내하기에 충분히 구체적입니다:
eagerness | 트리거 |
|---|---|
conservative | 포인터 또는 터치 다운 |
moderate | 데스크탑: 200ms 호버, 또는 이전 포인터 다운; 모바일: 뷰포트 휴리스틱 |
eager | Chrome 143+: 데스크탑 10ms 호버; 모바일 앵커가 뷰포트에 진입한 후 50ms |
immediate | 규칙이 관찰되는 즉시 |
규칙을 확장하기 전에 보수적으로 시작하고 예측 적중률, 전송된 바이트, 서버 부하, 탐색 개선을 측정하세요. 시간에 민감한 동작을 하드코딩하기 전에 Chrome의 유지 관리되는 eagerness 문서를 다시 확인하세요.
주의사항:
- 대역폭/CPU 비용. 각 사전 렌더링은 대략 전체 페이지 로드와 같습니다.
where를 신중하게 범위를 지정하고 (href_matches패턴, 로그아웃/체크아웃 제외) 소규모 사이트 외에는immediate를 피하세요. - 부작용이 조기에 발생합니다. 분석, 광고 및 로드 시 실행되는 모든 코드는 사용자가 탐색할 때가 아닌 사전 렌더링이 시작될 때 실행됩니다.
prerenderingchange이벤트 또는document.prerendering에서 부작용을 게이트하세요. - Chromium 전용. Safari와 Firefox는 스크립트를 무시합니다. 이는 점진적 개선(progressive enhancement)이며, 절대 회귀(regression)가 아닙니다.
LCP 최적화 체크리스트
- [ ] TTFB < 800ms (CDN, 엣지 캐싱 사용)
- [ ] LCP 리소스가 초기 HTML에서 발견 가능하고 우선순위가 지정됨; 트레이스에서 늦은 발견을 보여주는 경우에만 preload
- [ ] LCP 이미지 최적화 (WebP/AVIF, 올바른 크기)
- [ ] 중요 CSS 인라인 (< 14KB)
- [ ] <head>에 렌더링 차단 JavaScript 없음
- [ ] 글꼴이 텍스트 렌더링을 차단하지 않음 (font-display: swap)
- [ ] LCP 요소가 초기 HTML에 있음 (JS로 렌더링되지 않음)
- [ ] 다음 탐색 가능성을 위해 Speculation Rules 추가 (moderate eagerness)
LCP 요소 식별
이 스니펫은 현재 페이지 세션을 진단합니다. 필드 데이터가 아닙니다.
// LCP 요소 찾기
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP element:', lastEntry.element);
console.log('LCP time:', lastEntry.startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
INP: Interaction to Next Paint (상호작용에서 다음 페인트까지)
INP는 방문 중 클릭, 탭, 키 누름에 대한 응답성을 측정합니다. 입력 지연, 처리 시간, 프레젠테이션 지연을 별도로 진단하세요. 느린 상호작용은 핸들러 전 메인 스레드 경합, 값비싼 애플리케이션 작업 또는 그 이후의 지연된 렌더링과 관련될 수 있습니다.
필드 INP가 좋지 않거나 트레이스에서 느린 상호작용을 식별한 경우, 트레이스 해석, 양보(yielding) 패턴, 타사 및 렌더링 원인, 단일 세션 관찰자, 자사 기여도에 대한 INP 참조를 읽으세요.
CLS: Cumulative Layout Shift (누적 레이아웃 시프트)
CLS는 페이지 방문 전반에 걸친 예상치 못한 레이아웃 시프트를 측정합니다. 필드 속성 또는 트레이스를 사용하여 시프트된 노드와 트리거를 식별하세요. 보이는 피해자가 시프트를 유발했다고 가정하지 마세요.
필드 CLS가 좋지 않거나 트레이스에서 시프트를 보고하는 경우, 예약 공간 패턴, 동적 콘텐츠, 글꼴 및 애니메이션 수정, 디버깅 관찰자, 확인 체크리스트에 대한 CLS 참조를 읽으세요.
측정 소스
| 소스 | 용도 |
|---|---|
브라우저 성능 트레이스 (Chrome DevTools MCP: performance_start_trace) | 하나의 로드 또는 상호작용을 관찰하고 집중된 인사이트를 진단합니다. 사용 가능한 경우 포함된 CrUX 컨텍스트 사용 |
| CrUX 또는 Search Console | p75에서 집계된 실제 사용자 결과를 우선시합니다 |
| Lighthouse CLI 또는 PageSpeed Insights | DevTools 도구를 사용할 수 없을 때 통제된 실험실 대체 |
| 자사 RUM | 현재 프로덕션 경험을 경로, 기기, 릴리스 및 속성별로 세분화 |
원시 PerformanceObserver | 디버깅 중 하나의 페이지 세션 검사 |
성능을 Chrome DevTools MCP의 lighthouse_audit를 통해 라우팅하지 마세요. 해당 기능은 의도적으로 성능 이외의 Lighthouse 범주를 다룹니다. 단일 실험실 값을 필드 p75와 동등한 샘플인 것처럼 직접 비교하지 마세요.
프로덕션 수집을 추가하거나 검토할 때는 자사 RUM 참조를 읽으세요. web-vitals 라이브러리를 선호하세요. 원시 브라우저 API는 자체적으로 모든 Core Web Vital의 수명 주기 및 보고 규칙을 구현하지 않기 때문입니다.
프레임워크 빠른 수정
Next.js
// LCP: priority와 함께 next/image 사용
import Image from 'next/image';
<Image src="/hero.jpg" priority fill alt="Hero" />
// INP: 동적 임포트 사용
const HeavyComponent = dynamic(() => import('./Heavy'), { ssr: false });
// CLS: Image 컴포넌트가 자동으로 치수 처리
React
// LCP: head에서 preload
<link rel="preload" href="/hero.jpg" as="image" fetchpriority="high" />
// INP: 메모이제이션 및 useTransition 사용
const [isPending, startTransition] = useTransition();
startTransition(() => setExpensiveState(newValue));
// CLS: img 태그에 항상 치수 지정
Vue/Nuxt
<!-- LCP: preload와 함께 nuxt/image 사용 -->
<NuxtImg src="/hero.jpg" preload loading="eager" />
<!-- INP: 비동기 컴포넌트 사용 -->
<component :is="() => import('./Heavy.vue')" />
<!-- CLS: aspect-ratio CSS 사용 -->
<img :style="{ aspectRatio: '16/9' }" />
참조
- 상세 LCP 최적화 — LCP 트레이스가 발견, 로딩 또는 렌더링 지연을 가리킬 때 읽으세요
- 상세 INP 최적화 — 트레이스 또는 필드 속성이 느린 상호작용을 식별할 때 읽으세요
- 상세 CLS 최적화 — 트레이스 또는 필드 속성이 예상치 못한 시프트를 식별할 때 읽으세요
- web.dev LCP
- web.dev INP
- web.dev CLS
- 성능 스킬
