이 글은 누구를 위한 것인가
- 구글 검색 순위 개선을 위해 Core Web Vitals를 최적화해야 하는 프론트엔드 개발자
- PageSpeed Insights에서 낮은 점수를 받고 무엇을 고쳐야 할지 모르는 팀
- 웹 성능이 실제 비즈니스 지표(전환율, 이탈률)에 어떤 영향을 미치는지 이해하고 싶은 분
들어가며
웹 성능이 SEO에 영향을 준다는 것은 알지만, 정확히 어떤 지표가 어떻게 작용하는지 모호한 경우가 많다. 구글은 2021년부터 Core Web Vitals를 검색 순위 요소로 공식화했고, 2024년에는 FID를 INP로 교체하며 기준을 더 엄격하게 만들었다.
중요한 것은 이 지표들이 단순한 기술적 측정값이 아니라는 점이다. LCP는 사용자가 "이 페이지가 로드됐다"고 느끼는 시점, CLS는 "갑자기 클릭하려던 버튼이 밀렸다"는 경험, INP는 "버튼을 눌렀는데 반응이 없다"는 답답함과 직결된다. 성능 최적화는 사용자 경험 개선이고, 그 결과가 검색 순위로 반영된다.
실제 사례를 보면 LCP를 4.8초에서 2.1초로 개선했을 때 전환율이 15% 상승한 경우가 있다. 이 글에서는 각 지표를 실전에서 어떻게 개선하는지 구체적인 코드와 함께 다룬다.
이 글은 bluefoxdev.kr의 코어 웹 바이탈 최적화 가이드 포스트를 참고하고, 프론트엔드 구현 관점에서 확장하여 작성했습니다.
1. Core Web Vitals 개요
[Core Web Vitals 목표값]
LCP (Largest Contentful Paint): 가장 큰 콘텐츠 로드 시간
Good: 2.5초 이내
Needs Improvement: 2.5~4.0초
Poor: 4.0초 초과
CLS (Cumulative Layout Shift): 콘텐츠 이동 정도
Good: 0.1 이하
Needs Improvement: 0.1~0.25
Poor: 0.25 초과
INP (Interaction to Next Paint): 상호작용 반응 속도
Good: 200ms 이하
Needs Improvement: 200~500ms
Poor: 500ms 초과
세 지표 모두 "Good" 범위여야 구글 페이지 경험 점수가 높아진다.
측정 도구
- PageSpeed Insights: 실험실 데이터 + 실제 사용자 데이터(CrUX)
- Chrome DevTools Performance 탭: 로컬 디버깅
- Lighthouse: 자동화된 감사
- Search Console: 실제 사용자 기준 Core Web Vitals 현황
2. LCP 최적화: 가장 큰 콘텐츠를 빠르게 로드하기
LCP는 뷰포트에서 가장 큰 콘텐츠 요소(보통 히어로 이미지 또는 큰 텍스트 블록)가 렌더링되는 시간이다.
LCP 요소 찾기
// LCP 요소를 콘솔에서 직접 확인
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP element:', entry.element);
console.log('LCP time:', entry.startTime);
}
}).observe({type: 'largest-contentful-paint', buffered: true});
대부분의 페이지에서 LCP 요소는 히어로 섹션의 이미지 또는 배너 텍스트다.
이미지 최적화: LCP 개선의 핵심
차세대 이미지 포맷 사용: WebP는 JPEG 대비 25~34% 크기 감소, AVIF는 최대 50% 감소가 가능하다.
<!-- picture 태그로 브라우저 지원 여부에 따라 포맷 선택 -->
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="히어로 이미지" width="1200" height="600">
</picture>
LCP 이미지에 fetchpriority="high" 적용
<!-- 브라우저에게 이 이미지가 중요하다고 명시적으로 알림 -->
<img
src="hero.webp"
alt="히어로 이미지"
width="1200"
height="600"
fetchpriority="high"
loading="eager"
>
이것 하나로 LCP가 0.3~0.8초 개선되는 경우가 많다.
Next.js에서 LCP 이미지 최적화
import Image from 'next/image';
// priority prop이 fetchpriority="high"와 preload를 자동 적용
<Image
src="/hero.jpg"
alt="히어로 이미지"
width={1200}
height={600}
priority // LCP 이미지에는 반드시 추가
/>
서버 응답 시간(TTFB) 줄이기
LCP의 절반 이상은 네트워크에서 소요된다. TTFB(Time to First Byte)가 느리면 LCP도 느릴 수밖에 없다.
[TTFB 최적화 방법]
1. CDN 사용: 정적 자산을 전 세계 엣지에서 제공
2. 캐싱 헤더 설정: Cache-Control: public, max-age=31536000
3. SSG/ISR 활용: 서버 사이드 렌더링보다 미리 생성된 HTML이 빠름
4. 데이터베이스 쿼리 최적화: 서버 응답의 병목 확인
3. CLS 최적화: 갑작스러운 레이아웃 이동 막기
CLS는 사용자가 보는 동안 콘텐츠가 갑자기 이동하는 정도를 측정한다. 뉴스 기사를 읽다가 광고가 로드되면서 텍스트가 밀리는 경험이 CLS의 전형적인 예다.
이미지와 비디오 크기 명시
가장 흔한 CLS 원인이다. 이미지 크기를 명시하지 않으면 이미지가 로드되기 전까지 크기가 0이다가 로드 완료 시 크기가 생기면서 레이아웃이 이동한다.
<!-- 나쁜 예: 크기 없음 → 이미지 로드 시 레이아웃 이동 -->
<img src="product.jpg" alt="제품">
<!-- 좋은 예: width/height 명시 → 브라우저가 공간 미리 확보 -->
<img src="product.jpg" alt="제품" width="400" height="300">
CSS aspect-ratio로 처리하는 방법도 있다:
.product-image {
width: 100%;
aspect-ratio: 4 / 3; /* 공간을 미리 확보 */
}
폰트 로딩으로 인한 CLS 방지
웹폰트가 로드되면서 텍스트 크기가 변하면 CLS가 발생한다.
@font-face {
font-family: 'Pretendard';
src: url('/fonts/pretendard.woff2') format('woff2');
font-display: optional; /* 폰트 로드 대기 없이 바로 폴백 폰트 사용 */
}
font-display: swap 대신 optional을 쓰면 CLS 없이 폰트를 로드한다. 단, 처음 방문 시에는 폴백 폰트로 렌더링된다.
동적으로 삽입되는 콘텐츠 관리
광고, 배너, 동의 팝업 등 나중에 삽입되는 콘텐츠는 미리 공간을 확보해야 한다.
/* 광고 컨테이너: 광고가 로드되기 전에도 공간 유지 */
.ad-container {
min-height: 250px; /* 광고의 예상 높이 */
width: 100%;
}
4. INP 최적화: 상호작용에 즉각 반응하기
INP(Interaction to Next Paint)는 FID를 대체한 지표다. FID가 첫 번째 상호작용만 측정했다면, INP는 페이지 전체 수명 동안의 모든 상호작용을 측정한다.
메인 스레드 차단 줄이기
INP가 나쁜 가장 흔한 원인은 JavaScript가 메인 스레드를 오래 차단하는 것이다.
Long Task 찾기
// 50ms 이상 걸리는 작업을 Long Task로 분류
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long Task:', entry.duration + 'ms', entry.name);
}
}
}).observe({type: 'longtask', buffered: true});
무거운 계산을 작게 쪼개기
// 나쁜 예: 메인 스레드를 오래 차단
function processLargeList(items) {
return items.map(item => heavyTransform(item)); // 동기, 블로킹
}
// 좋은 예: 청크로 나눠서 처리
async function processLargeListAsync(items) {
const CHUNK_SIZE = 50;
const results = [];
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE);
results.push(...chunk.map(item => heavyTransform(item)));
// 브라우저에게 제어를 넘겨 렌더링/상호작용 처리 허용
await new Promise(resolve => setTimeout(resolve, 0));
}
return results;
}
이벤트 핸들러 최적화
// 나쁜 예: 클릭 시마다 무거운 계산
button.addEventListener('click', () => {
const result = heavyCalculation(); // INP 악화
updateUI(result);
});
// 좋은 예: 계산은 미리, UI 업데이트만 클릭 시
let precomputedResult = null;
// 페이지 로드 시 백그라운드에서 미리 계산
requestIdleCallback(() => {
precomputedResult = heavyCalculation();
});
button.addEventListener('click', () => {
if (precomputedResult) {
updateUI(precomputedResult); // 빠른 UI 업데이트만
}
});
5. 이미지 전략 총정리
이미지 최적화는 LCP, CLS, 전체 페이지 크기 모두에 영향을 미치는 가장 효과적인 최적화 포인트다.
[이미지 최적화 체크리스트]
포맷
□ JPEG → WebP 변환 (25~34% 크기 감소)
□ WebP → AVIF 변환 고려 (추가 50% 감소, 브라우저 지원 확인)
□ 아이콘/로고는 SVG 사용
크기
□ width, height 속성 항상 명시 (CLS 방지)
□ srcset으로 화면 크기별 적합한 이미지 제공
□ 실제 표시 크기보다 큰 이미지 사용하지 않기
로딩 전략
□ LCP 이미지: fetchpriority="high", loading="eager"
□ 뷰포트 밖 이미지: loading="lazy"
□ 히어로 이미지: preload link 태그로 조기 다운로드
압축
□ 무손실 압축 도구 사용 (Squoosh, Sharp)
□ 이미지 품질 설정 최적화 (WebP 80~85% 품질로 충분)
6. 실제 개선 사례
이커머스 사이트의 Core Web Vitals 개선 사례:
개선 전 상태
- LCP: 4.8초 (Poor)
- CLS: 0.32 (Poor)
- INP: 380ms (Needs Improvement)
적용한 변경사항
- 히어로 이미지 → WebP 전환 +
fetchpriority="high"추가 - 상품 목록 이미지에
width/height명시 (CLS 원인이었음) - 장바구니 버튼 클릭 핸들러 최적화 (재고 확인 API 호출을 비동기로)
- Google Fonts → 로컬 폰트 파일로 변경 +
font-display: optional
개선 후 결과
- LCP: 4.8초 → 2.1초 (Good)
- CLS: 0.32 → 0.04 (Good)
- INP: 380ms → 145ms (Good)
- 전환율: 15% 상승
맺으며
Core Web Vitals는 구글이 "빠르고 안정적이고 반응성 좋은 사이트"를 측정하는 방식이다. 숫자를 맞추는 것이 목적이 아니라, 그 숫자 뒤에 있는 사용자 경험을 개선하는 것이 목적이다.
측정부터 시작하자. PageSpeed Insights에서 현재 점수를 확인하고, 가장 낮은 지표 하나를 집중적으로 개선한다. 보통 LCP가 가장 직접적인 영향을 미치고, 개선 효과도 빠르게 확인된다.
히어로 이미지를 WebP로 바꾸고 fetchpriority="high"를 추가하는 것만으로도 LCP가 크게 개선되는 경우가 많다. 완벽한 최적화보다 지금 당장 할 수 있는 것부터 시작하는 것이 중요하다.
출처 및 참고 자료