TL;DR - 핵심 요약
- INP(Interaction to Next Paint)는 2024년 3월 FID를 대체한 코어 웹 바이탈 지표. 모바일 통과율은 전 세계 77%, 상위 1,000개 사이트는 63%만 통과
- INP는 Input Delay + Processing Duration + Presentation Delay 세 구간으로 나뉘며, 각 구간별 최적화 전략이 다름
scheduler.yield()하나로 긴 작업을 분할하면 INP가 극적으로 개선됨 (Chrome 129+, Firefox 142+ 지원)- 서드파티 스크립트 10개 이상이면 INP 500ms 초과 가능 — GTM 컨테이너 분리가 핵심
- redBus 사례: INP 72% 개선, 매출 +7% (web.dev 확인). 전환율 +6~7.2%는 일부 국가 기준(redBus Medium 글)
INP가 중요한 이유: FID와 무엇이 다른가
INP(Interaction to Next Paint)는 사용자가 페이지와 상호작용(클릭, 탭, 키보드 입력)한 후 다음 프레임이 화면에 그려질 때까지의 시간을 측정합니다. 2024년 3월 12일부터 FID를 공식 대체했습니다.
FID는 첫 번째 입력의 지연 시간만 측정했습니다. 페이지 로드 직후 한 번의 클릭만 보는 것이죠. 반면 INP는 페이지 수명 전체에서 발생하는 모든 상호작용 중 가장 느린 것을 기준으로 삼습니다. 필터 변경, 아코디언 열기, 장바구니 추가처럼 실제 사용자 행동에서 발생하는 병목을 포착합니다.
| 등급 | 시간 | 의미 |
|---|---|---|
| 좋음 | 200ms 이하 | 사용자가 즉각 반응으로 인식 |
| 개선 필요 | 200~500ms | 눈에 띄는 지연 |
| 나쁨 | 500ms 초과 | 사용자가 "먹통"으로 인식 |
현재 통과율
출처: DebugBear CrUX Dashboard ↗
| 구분 | INP 통과율 |
|---|---|
| 전 세계 모바일 | 77% |
| 전 세계 데스크톱 | 97% |
| 상위 1,000 사이트 모바일 | 63% |
| 3개 CWV 모두 통과 | 53% |
한국은 전 세계에서 가장 우수한 INP 수치(p75 기준 75ms)를 기록하고 있습니다. 다만 이 수치는 CrUX 전체 오리진 평균이며, 서드파티 스크립트가 15개 이상 탑재된 대형 이커머스 사이트는 개별적으로 200ms를 초과하는 경우가 드물지 않습니다.
2026년 3월 코어 업데이트와 INP
2026년 3월 코어 업데이트 이후 업계 분석에서는 INP의 랭킹 가중치가 LCP, CLS와 동등 수준으로 상향되었다는 관측이 나오고 있습니다.
| INP 범위 | 순위 변동 (업계 분석) |
|---|---|
| 200~500ms | 평균 -0.8 포지션 |
| 500ms 초과 | -2~4 포지션 |
주의: 위 수치는 서드파티 SEO 업체들의 분석 기준이며, Google이 공식 확인한 수치가 아닙니다. Google은 개별 지표의 정확한 랭킹 가중치를 공개하지 않습니다.
Google 공식 문서에 따르면 페이지 경험 평가는 일반적으로 페이지 단위로 이루어지며, 일부 평가만 사이트 전체를 고려합니다. 업계에서는 이번 업데이트로 사이트 레벨 평가가 강화되었다는 분석이 있지만, Google이 이를 공식 확인하지는 않았습니다. 그럼에도 INP가 나쁜 페이지가 사용자 경험에 부정적 영향을 미치는 것은 사실이므로, 사이트 전반의 INP 관리가 중요합니다.
INP의 세 구간 이해하기
INP를 최적화하려면 지연이 어디서 발생하는지 알아야 합니다. INP는 세 구간의 합입니다.
[사용자 클릭] → Input Delay → Processing Duration → Presentation Delay → [화면 업데이트]
1. Input Delay (입력 지연)
사용자가 클릭하거나 키를 눌렀지만, 이벤트 핸들러가 아직 실행되지 못하는 시간입니다. 메인 스레드가 다른 작업(긴 JavaScript 태스크, 서드파티 스크립트)으로 바쁘면 Input Delay가 길어집니다.
주요 원인: Long Task가 메인 스레드를 점유하고 있는 동안 사용자가 상호작용
2. Processing Duration (처리 시간)
이벤트 핸들러 코드가 실제로 실행되는 시간입니다. 핸들러 내부에서 무거운 계산, DOM 조작, 상태 업데이트를 동기적으로 처리하면 이 구간이 길어집니다.
주요 원인: 핸들러 내부의 동기적 무거운 작업
3. Presentation Delay (표현 지연)
핸들러 실행이 끝난 후 브라우저가 실제로 화면을 그리기까지의 시간입니다. 레이아웃 재계산, 스타일 재계산, 페인트 작업이 여기 포함됩니다.
주요 원인: Layout Thrashing, 과도한 DOM 변경, 대규모 리렌더링
핵심: 세 구간 중 어디가 병목인지 모르면 최적화 방향이 틀립니다. Chrome DevTools의 Performance 패널에서 상호작용을 기록하면 각 구간의 소요 시간을 확인할 수 있습니다.
실전 최적화 전략
전략 1: scheduler.yield()로 긴 작업 분할
scheduler.yield()는 INP 최적화를 위해 만들어진 API입니다. 긴 함수 중간에 await scheduler.yield()를 넣으면 브라우저에 제어권을 돌려주어, 대기 중인 사용자 입력을 처리하고 화면을 업데이트할 수 있습니다.
// Before: 모든 작업이 하나의 Long Task로 실행
function saveSettings() {
validateForm(); // 50ms
showSpinner(); // 10ms
updateUI(); // 80ms
saveToDatabase(); // 100ms
sendAnalytics(); // 40ms
// 총 280ms — 사용자 입력이 280ms 동안 차단됨
}
// After: scheduler.yield()로 분할
async function saveSettings() {
validateForm();
showSpinner();
updateUI();
await scheduler.yield(); // 브라우저가 화면을 그리고, 대기 중인 입력을 처리
saveToDatabase();
await scheduler.yield();
sendAnalytics();
// UI 업데이트까지는 ~140ms, 나머지는 비동기로 처리
}
브라우저 지원 현황 (2026년 4월 기준):
| 브라우저 | 지원 버전 |
|---|---|
| Chrome | 129+ |
| Edge | 129+ |
| Firefox | 142+ |
| Safari | 미지원 |
Safari 대응을 위한 폴백 패턴:
async function yieldToMain() {
if (typeof scheduler !== 'undefined' && scheduler.yield) {
await scheduler.yield();
} else {
// setTimeout(0)은 scheduler.yield()보다 우선순위 관리가 부정확하지만
// Long Task를 분할하는 기본 효과는 동일
await new Promise(resolve => setTimeout(resolve, 0));
}
}
// 사용
async function handleFilterChange(filters) {
applyFiltersToState(filters);
renderFilterUI(filters);
await yieldToMain();
filterProducts(filters); // 무거운 계산
await yieldToMain();
updateProductList(); // DOM 업데이트
trackFilterEvent(filters); // 분석
}
scheduler.yield() vs setTimeout(0) 차이점: scheduler.yield()는 양보 후 원래 작업이 동일한 우선순위로 다시 스케줄링됩니다. setTimeout(0)은 태스크 큐의 맨 뒤로 밀리기 때문에, 다른 대기 중인 태스크에 의해 재개가 지연될 수 있습니다. 실질적으로 scheduler.yield()가 더 예측 가능한 동작을 보여줍니다.
전략 2: Web Worker로 메인 스레드 분리
메인 스레드에서 무거운 계산을 실행하면 Input Delay와 Processing Duration이 모두 길어집니다. Web Worker를 사용하면 계산을 별도 스레드로 옮겨 메인 스레드를 자유롭게 유지할 수 있습니다.
실측 사례:
| 사례 | Before | After | 개선 |
|---|---|---|---|
| 분석 대시보드 (50K 데이터 포인트 계산) | 680ms | 145ms | -79% |
| SaaS 애플리케이션 | 650ms | 150ms | -77% |
// worker.js — 별도 스레드에서 실행
self.onmessage = function(e) {
const { products, filters } = e.data;
// 무거운 필터링/정렬 로직
const filtered = products
.filter(p => matchesFilters(p, filters))
.sort((a, b) => calculateScore(b, filters) - calculateScore(a, filters));
// 결과만 메인 스레드로 전달
self.postMessage({ filtered, count: filtered.length });
};
// main.js — 메인 스레드
const filterWorker = new Worker('/worker.js');
function handleFilterChange(filters) {
// UI는 즉시 업데이트
showLoadingState();
// 무거운 계산은 Worker에게 위임
filterWorker.postMessage({ products: productData, filters });
}
filterWorker.onmessage = function(e) {
const { filtered, count } = e.data;
hideLoadingState();
renderProducts(filtered);
updateResultCount(count);
};
Web Worker가 효과적인 상황:
- 대량 데이터 필터링, 정렬, 집계
- JSON 파싱 (대용량 API 응답)
- 이미지 처리, 압축
- 복잡한 유효성 검증 로직
주의사항: Worker는 DOM에 접근할 수 없으므로, 계산 결과만 메인 스레드로 전달하고 DOM 업데이트는 메인 스레드에서 처리해야 합니다. 또한 postMessage로 데이터를 전달할 때 직렬화 비용이 발생하므로, 수 MB 이상의 데이터를 자주 주고받는 경우 Transferable 객체나 SharedArrayBuffer 사용을 검토하세요.
전략 3: 서드파티 스크립트 분리
서드파티 스크립트는 INP의 가장 흔한 킬러입니다. 개별 스크립트가 50~100ms의 지연을 추가하고, 10개 이상이면 누적 500ms를 쉽게 넘깁니다.
한국 이커머스 사이트의 전형적인 서드파티 스크립트 구성:
| 스크립트 | 용도 | 추정 INP 영향 |
|---|---|---|
| Google Analytics 4 | 분석 | 30~50ms |
| GTM 컨테이너 | 태그 관리 | 50~150ms |
| 카카오톡 채널 위젯 | 채팅 | 40~80ms |
| 네이버 페이 SDK | 결제 | 30~60ms |
| Facebook Pixel | 리타겟팅 | 30~50ms |
| 각종 소셜 로그인 SDK | 인증 | 20~40ms 각 |
| A/B 테스팅 도구 | 실험 | 40~80ms |
| 챗봇 위젯 | 고객 지원 | 50~100ms |
GTM 컨테이너 분리 전략:
GTM은 dataLayer.push()가 클릭 이벤트 핸들러 내부에서 실행될 때 INP를 악화시킵니다. 모든 태그가 하나의 컨테이너에 들어 있으면, 이벤트 핸들러가 완료되기 전에 비필수 태그의 로직까지 실행됩니다.
<!-- 핵심 컨테이너: GA4만 포함, 페이지 로드 시 즉시 실행 -->
<script>
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-CORE');
</script>
<!-- 비핵심 컨테이너: 리마케팅, 챗봇, A/B 테스트 등 -->
<!-- requestIdleCallback으로 메인 스레드가 유휴 상태일 때 로드 -->
<script>
function loadDeferredGTM() {
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayerDeferred','GTM-DEFERRED');
}
if ('requestIdleCallback' in window) {
requestIdleCallback(loadDeferredGTM, { timeout: 5000 });
} else {
setTimeout(loadDeferredGTM, 3000);
}
</script>
클릭 핸들러에서 dataLayer.push 분리:
// Before: 클릭 핸들러 안에서 dataLayer.push가 동기적으로 실행
button.addEventListener('click', () => {
addToCart(productId);
updateCartUI();
dataLayer.push({ event: 'add_to_cart', product_id: productId }); // INP 증가
});
// After: 분석 이벤트를 다음 프레임으로 미루기
button.addEventListener('click', () => {
addToCart(productId);
updateCartUI();
// 화면 업데이트 후 분석 이벤트 전송
requestAnimationFrame(() => {
setTimeout(() => {
dataLayer.push({ event: 'add_to_cart', product_id: productId });
}, 0);
});
});
requestAnimationFrame + setTimeout(0) 패턴은 현재 프레임의 페인트가 완료된 후 다음 태스크에서 실행되므로, 분석 이벤트가 INP 측정 구간에 포함되지 않습니다.
전략 4: Layout Thrashing 방지
Layout Thrashing은 DOM 읽기와 쓰기를 번갈아 실행할 때 발생합니다. 브라우저가 매번 강제로 레이아웃을 재계산해야 하므로 Presentation Delay가 급증합니다.
// Before: Layout Thrashing — 매 반복마다 강제 레이아웃
function resizeCards(cards) {
cards.forEach(card => {
const width = card.offsetWidth; // 읽기 → 강제 레이아웃
card.style.height = width * 1.5 + 'px'; // 쓰기 → 레이아웃 무효화
});
}
// After: 읽기와 쓰기를 분리
function resizeCards(cards) {
// 1단계: 모든 읽기를 먼저 수행
const widths = cards.map(card => card.offsetWidth);
// 2단계: 모든 쓰기를 한 번에 수행 (레이아웃 재계산은 1회만 발생)
cards.forEach((card, i) => {
card.style.height = widths[i] * 1.5 + 'px';
});
}
강제 레이아웃을 유발하는 속성들: offsetWidth, offsetHeight, clientWidth, clientHeight, scrollTop, getBoundingClientRect(). 이 속성들을 읽은 직후 스타일을 변경하면 브라우저가 최신 레이아웃을 계산해야 하므로 강제 레이아웃이 발생합니다.
전략 5: 이벤트 핸들러 최적화
스크롤, 리사이즈, 키 입력 이벤트에 무제한 핸들러를 등록하면 Input Delay가 급증합니다.
// Before: 키 입력마다 Redux 상태 업데이트 + API 호출
searchInput.addEventListener('input', (e) => {
store.dispatch(updateSearchQuery(e.target.value)); // 동기 상태 업데이트
fetchSuggestions(e.target.value); // API 호출
});
// After: 디바운스 + 상태 업데이트 분리
searchInput.addEventListener('input', (e) => {
// UI 업데이트만 즉시 처리 (사용자에게 반응 보여주기)
e.target.style.borderColor = '#3B82F6';
});
// 상태 업데이트와 API 호출은 디바운스
const debouncedSearch = debounce((value) => {
store.dispatch(updateSearchQuery(value));
fetchSuggestions(value);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// blur 시점에 최종 상태 동기화
searchInput.addEventListener('blur', (e) => {
store.dispatch(updateSearchQuery(e.target.value));
});
스크롤 핸들러: passive: true 옵션을 반드시 사용하세요.
// passive: true는 브라우저에게 "이 핸들러는 preventDefault()를 호출하지 않는다"고 알려줌
// → 브라우저가 핸들러 완료를 기다리지 않고 즉시 스크롤 가능
window.addEventListener('scroll', handleScroll, { passive: true });
전략 6: 선택적 하이드레이션 (React)
React 18의 Selective Hydration은 <Suspense>를 사용해 컴포넌트별로 하이드레이션 우선순위를 조절합니다. 사용자가 상호작용하는 컴포넌트를 먼저 하이드레이션하고, 나머지는 나중에 처리합니다.
Wix 사례:
출처: Wix Engineering - 40% Faster Interaction ↗
Wix는 React 18 Suspense 기반 Selective Hydration을 적용하여 INP 약 40% 개선, JS 페이로드 20% 감소를 달성했습니다. 개별 컴포넌트를 리팩토링하지 않고도 하이드레이션 전략 변경만으로 이 수치를 달성한 것이 핵심입니다.
import { Suspense, lazy } from 'react';
// 상호작용이 필요한 컴포넌트: 즉시 하이드레이션
import ProductActions from './ProductActions';
// 하단/뷰포트 밖 컴포넌트: 지연 하이드레이션
const Reviews = lazy(() => import('./Reviews'));
const Recommendations = lazy(() => import('./Recommendations'));
function ProductPage({ product }) {
return (
<div>
<ProductInfo product={product} />
{/* 즉시 인터랙션 가능 */}
<ProductActions product={product} />
{/* 사용자가 스크롤해서 접근할 때 하이드레이션 */}
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={product.id} />
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations productId={product.id} />
</Suspense>
</div>
);
}
사용자가 아직 하이드레이션되지 않은 컴포넌트를 클릭하면, React가 해당 컴포넌트의 하이드레이션을 자동으로 우선순위 상향합니다.
실제 사례 분석
redBus: INP 72% 개선, 매출 +7%
인도 최대 버스 예매 플랫폼 redBus는 다음 최적화를 적용했습니다.
| 최적화 항목 | 적용 내용 |
|---|---|
| 스크롤 핸들러 디바운스 | 무제한 스크롤 이벤트 → requestAnimationFrame 기반 쓰로틀링 |
| Redux 상태 업데이트 시점 변경 | 키 입력마다 → blur 이벤트에서만 동기화 |
| 불필요한 리렌더링 제거 | React.memo, useMemo로 불필요한 재계산 방지 |
결과:
| 지표 | 변화 |
|---|---|
| INP | 72% 개선 |
| 전환율 | +6~7.2% (일부 국가, redBus Medium 글 기준) |
| 매출 | +7% |
Wix: Selective Hydration으로 INP 40% 개선
Wix의 사례가 의미 있는 이유는 플랫폼 수준의 변경이라는 점입니다. 수백만 개의 개별 사이트 코드를 수정하지 않고, 하이드레이션 전략만 바꿔서 전체 플랫폼의 INP를 개선했습니다.
전환율과 매출에 미치는 영향
출처: Google 이커머스 CWV 연구 2024 ↗ · Rakuten 24 Case Study ↗
| 사례 | 결과 |
|---|---|
| Google 2024 이커머스 연구 | CWV "Good" 달성 시 모바일 전환율 24% 향상 |
| Rakuten 24 | CWV 최적화 후 방문자당 매출 53% 증가, 전환율 33% 증가 |
| Amazon (통상 인용) | 100ms 지연 = 매출 1% 손실 |
한국에서 이 수치가 특히 중요한 이유가 있습니다. **모바일 쇼핑 비중이 온라인 매출의 73%**를 차지하는 한국 시장에서, 모바일 INP 최적화는 직접적인 매출 영향으로 이어집니다.
한국 사이트 특수 상황
서드파티 스크립트 과부하
한국 이커머스 사이트는 글로벌 사이트 대비 서드파티 의존도가 높습니다. 카카오톡 채널, 네이버 페이 SDK, 다수의 소셜 로그인(카카오, 네이버, Apple, Google), 채널톡/카카오 상담 위젯 등이 기본으로 탑재됩니다.
우선순위 기반 로딩 전략:
// 1순위: 결제 관련 (사용자 행동에 직접 영향)
// → 페이지 로드 시 async로 로드
// 네이버 페이, 카카오 페이
// 2순위: 분석 (비즈니스에 중요하지만 INP에 영향)
// → requestIdleCallback 또는 지연 로드
// GA4, GTM 핵심 컨테이너
// 3순위: 마케팅/리타겟팅 (사용자 경험에 직접 영향 없음)
// → 페이지 로드 완료 후 5초 이상 지연
// Facebook Pixel, 크리테오, 기타 리마케팅
// 4순위: 위젯 (스크롤 또는 시간 기반 트리거)
// → 사용자 상호작용 또는 스크롤 시점에 로드
// 채팅 위젯, 소셜 공유 버튼
// 구현 예시
function loadByPriority() {
// 2순위: 유휴 시간에 분석 스크립트 로드
if ('requestIdleCallback' in window) {
requestIdleCallback(() => loadAnalytics(), { timeout: 3000 });
} else {
setTimeout(() => loadAnalytics(), 2000);
}
// 3순위: 5초 후 마케팅 스크립트
setTimeout(() => loadMarketingScripts(), 5000);
// 4순위: 스크롤 시 채팅 위젯
const chatObserver = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadChatWidget();
chatObserver.disconnect();
}
});
chatObserver.observe(document.querySelector('#content-bottom'));
}
window.addEventListener('load', loadByPriority);
네이버와 Google의 CWV 차이
네이버는 CWV를 랭킹 시그널로 사용하지 않습니다. 하지만 INP 최적화를 무시해도 된다는 의미는 아닙니다.
- Google 검색 트래픽: 한국에서도 Google 검색 비중이 꾸준히 증가하고 있으며, 특히 기술, 영문 콘텐츠, 젊은 사용자층에서 비중이 높습니다
- 사용자 경험: 네이버 랭킹에 영향이 없더라도, 느린 상호작용은 이탈률과 전환율에 직접 영향을 미칩니다
- 네이버 스마트스토어: 스마트스토어 내부에서는 스크립트를 제어할 수 없지만, 자체 사이트에서 네이버 페이/로그인 SDK를 사용할 때 INP 관리가 필요합니다
측정 및 디버깅 방법
Chrome DevTools로 INP 디버깅
- Performance 패널 열기 (F12 → Performance)
- 녹화 시작 후 페이지에서 다양한 상호작용 수행
- Interactions 트랙에서 각 상호작용의 세 구간(Input Delay, Processing, Presentation) 확인
- 가장 긴 상호작용이 INP 후보
web-vitals 라이브러리로 필드 데이터 수집
// attribution 빌드에서 import해야 metric.attribution 사용 가능
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
console.log('INP:', metric.value, 'ms');
console.log('INP Rating:', metric.rating); // 'good', 'needs-improvement', 'poor'
// attribution 데이터로 원인 파악
if (metric.attribution) {
console.log('Event type:', metric.attribution.interactionType);
console.log('Input Delay:', metric.attribution.inputDelay);
console.log('Processing Duration:', metric.attribution.processingDuration);
console.log('Presentation Delay:', metric.attribution.presentationDelay);
console.log('Target:', metric.attribution.interactionTarget);
}
// GA4로 전송
gtag('event', 'web_vitals', {
metric_name: 'INP',
metric_value: metric.value,
metric_rating: metric.rating,
interaction_target: metric.attribution?.interactionTarget || 'unknown',
});
}, { reportAllChanges: true });
CrUX 데이터로 실제 사용자 INP 확인
PageSpeed Insights, CrUX Dashboard, Search Console CWV 보고서에서 실제 사용자(필드) 데이터를 확인할 수 있습니다. Lab 데이터(Lighthouse)와 필드 데이터는 차이가 있을 수 있으므로, 최적화 효과 검증은 반드시 필드 데이터 기준으로 해야 합니다.
INP 최적화 체크리스트
Input Delay 줄이기
- 50ms 이상의 Long Task를
scheduler.yield()또는setTimeout(0)으로 분할 - 서드파티 스크립트를 우선순위별로 분류하고 비핵심 스크립트 지연 로드
- GTM 컨테이너를 핵심/비핵심으로 분리
- 페이지 로드 시 실행되는 JavaScript 최소화 (코드 스플리팅, tree-shaking)
Processing Duration 줄이기
- 무거운 계산을 Web Worker로 이동
- 이벤트 핸들러 내부에서 불필요한 동기 작업 제거
- 스크롤/리사이즈 핸들러에 디바운스 또는 쓰로틀 적용
- Redux/상태 관리 업데이트를 키 입력마다가 아니라 blur/submit 시점에 동기화
-
dataLayer.push()를 클릭 핸들러 밖으로 분리 (requestAnimationFrame+setTimeout패턴)
Presentation Delay 줄이기
- DOM 읽기/쓰기 분리 (Layout Thrashing 방지)
-
content-visibility: auto적용으로 화면 밖 콘텐츠 렌더링 스킵 - React:
<Suspense>를 활용한 Selective Hydration 적용 - CSS
contain속성으로 레이아웃 재계산 범위 제한
자주 묻는 질문
Q1: INP 200ms를 넘는 가장 흔한 원인은 무엇인가요?
서드파티 스크립트 과다가 가장 흔한 원인입니다. GTM 컨테이너 하나에 10개 이상의 태그가 들어 있으면, 클릭 이벤트 시 모든 태그의 트리거가 평가됩니다. 두 번째는 키 입력마다 상태 관리 라이브러리를 동기적으로 업데이트하는 패턴, 세 번째는 전체 페이지 하이드레이션으로 인한 메인 스레드 점유입니다.
Q2: Lighthouse에서는 INP가 좋은데 실제 사용자 데이터는 나쁩니다. 왜 그런가요?
Lighthouse는 합성 테스트(Lab)로, 서드파티 스크립트가 완전히 로드되지 않았거나 테스트 시 상호작용이 제한적일 수 있습니다. 실제 사용자는 서드파티 스크립트가 모두 로드된 상태에서 다양한 상호작용을 하므로 INP가 높게 나옵니다. 필드 데이터(CrUX)를 기준으로 최적화해야 합니다.
Q3: scheduler.yield()를 Safari에서 쓸 수 없는데 어떻게 하나요?
이 글에서 소개한 yieldToMain() 폴백 함수를 사용하세요. setTimeout(0)은 scheduler.yield()만큼 정교하지는 않지만, Long Task를 분할하는 기본 효과는 동일합니다. Safari 지원이 추가될 때까지 폴백 패턴을 유지하면 됩니다.
Q4: INP 최적화가 네이버 순위에도 영향이 있나요?
네이버는 CWV를 랭킹 시그널로 사용하지 않습니다. 하지만 INP가 나쁜 사이트는 사용자 이탈률이 높고 체류 시간이 짧습니다. 네이버도 사용자 행동 시그널을 다양한 방식으로 활용하므로, 간접적인 영향은 있을 수 있습니다. 무엇보다 전환율과 매출은 검색엔진과 무관하게 사용자 경험에 의해 결정됩니다.
Q5: INP 최적화를 어디서부터 시작해야 하나요?
1단계: web-vitals 라이브러리로 어떤 상호작용이 가장 느린지 식별하세요. 2단계: Chrome DevTools Performance 패널에서 해당 상호작용의 세 구간 중 어디가 병목인지 확인하세요. 3단계: 병목 구간에 맞는 전략을 적용하세요 (Input Delay → 서드파티 정리, Processing → scheduler.yield()/Worker, Presentation → Layout Thrashing 수정).
마무리
INP 최적화의 핵심은 "메인 스레드를 비워두는 것"입니다. 사용자가 클릭했을 때 메인 스레드가 다른 일을 하고 있지 않으면 INP는 자연히 좋아집니다.
가장 즉각적인 효과를 볼 수 있는 순서는 다음과 같습니다.
- 서드파티 스크립트 정리 — 비용 대비 효과가 가장 큰 작업
scheduler.yield()로 긴 핸들러 분할 — 코드 변경이 적고 효과가 확실- 이벤트 핸들러 디바운스 및 상태 업데이트 최적화 — 검색, 필터 등 빈번한 상호작용
한국 이커머스 사이트라면, 카카오/네이버 SDK 포함 서드파티 스크립트의 로딩 전략부터 점검하세요. 전체 스크립트를 한 번에 로드하는 것에서 우선순위 기반 분리로 전환하는 것만으로도 상당한 INP 개선을 기대할 수 있습니다.
INP 최적화 진단이 필요하시면 XEO 무료 진단을 신청하세요.