Interaction to Next Paint를 개선하려면 느린 상호작용을 재현하고 핸들러 시작 전, 실행 중, 다음 프레임 표시 전 중 어디서 지연되는지 찾습니다. 불필요한 작업을 제거하고 정확성을 유지할 수 있는 범위에서 나머지를 나누며 렌더링 비용을 줄입니다. 로딩 점수만 좋아졌다고 판단하지 말고 대화형 기록과 실제 사용자 측정으로 확인합니다.
INP는 75번째 백분위수에서 200밀리초 이하면 좋음입니다. 방문 중의 반응성을 나타내므로 페이지가 준비된 것처럼 보인 뒤 한참 후의 행동이 값을 결정할 수 있습니다. 로딩이 빨라도 상품 필터나 탐색 메뉴에 문제가 있을 수 있습니다.
INP가 측정하는 범위와 제외하는 범위를 이해합니다
INP는 측정 대상 클릭, 탭, 키보드 상호작용을 평가합니다. 관련 상호작용 작업 이후 다음 페인트까지의 지연을 고려합니다. 이벤트 핸들러 실행 시간만을 뜻하지 않으며 클릭 뒤 이어지는 모든 업무 과정의 완료 시간도 아닙니다.
검색 화면은 비동기 요청이 결과를 받는 동안 즉시 로딩 상태를 보여 줄 수 있습니다. 빠른 피드백은 반응성을 개선하지만 네트워크 응답을 빠르게 하지는 않습니다. 방문자의 목표가 나중의 결과에 달려 있다면 작업 완료 시간도 측정합니다.
대체로 방문 중 가장 느린 상호작용을 반영하며 상호작용이 많은 방문에서는 이상값을 일부 처리합니다. 측정 대상 상호작용이 없는 방문에는 INP 값이 없습니다. 누락을 0으로 바꾸거나 즉시 반응한다는 증거로 해석하지 않습니다.
INP는 2024년 3월 First Input Delay를 대신해 Core Web Vital이 되었습니다. FID는 첫 유효 상호작용의 초기 입력 지연만 측정했으며 방문 전체의 완전한 반응을 설명하지 못했습니다. 따라서 FID 중심의 오래된 조언은 나중의 느린 상호작용이나 비용이 큰 표시 작업을 놓칠 수 있습니다.
Core Web Vitals 입문 가이드는 기준과 백분위수 평가를 설명합니다. 이런 집단 수준 결과는 개발 환경에서 의도적으로 재현한 상호작용 하나와 구분해야 합니다.
지연을 세 구간으로 나눕니다
각 구간은 다른 조사 방향을 제시합니다. Google의 INP 최적화 지침은 이 구분을 메인 스레드 작업과 연결합니다.
| 구간 | 일어나는 일 | 확인 대상 |
|---|---|---|
| 입력 지연 | 핸들러가 시작되기 전 입력이 기다립니다 | 앞선 긴 작업, 타이머, 스크립트 |
| 처리 시간 | 상호작용 핸들러가 실행됩니다 | 계산, 동기 작업, 반복 핸들러 |
| 표시 지연 | 브라우저가 다음 프레임을 준비하고 표시합니다 | 스타일, 레이아웃, 페인트, 큰 DOM 변경 |
짧은 핸들러도 다른 스크립트가 시작을 막으면 느리게 느껴집니다. 빨리 끝나더라도 결과 레이아웃이 오래 걸릴 수 있습니다. 함수 하나의 실행 시간만 보면 두 경우를 모두 놓칩니다.
문제 트레이스에서 가장 긴 구간부터 시작합니다. 개선한 뒤에는 전체 상호작용을 다시 봅니다. 작업을 다른 구간으로 옮기기만 하면 총 지연이 줄지 않을 수 있습니다. 반면 업데이트를 작게 만들면 여러 구간이 함께 줄어들 수 있습니다.
대표적인 상태에서 실제 행동을 기록합니다
Chrome DevTools의 Performance 패널에서 행동 자체를 기록합니다. 기록 시작, 상호작용 재현, 종료 후 타임라인 확인 순서로 진행합니다. 열린 메뉴, 채워진 장바구니, 펼친 목록, 스크롤 후 쌓인 콘텐츠처럼 관련 상태를 포함합니다.
새로 연 빈 페이지에서는 여러 번 업데이트한 뒤 생기는 문제가 재현되지 않을 수 있습니다. 반대로 관계없는 확장 프로그램은 실제 방문자에게 없는 작업을 추가할 수 있습니다. 결과에 영향을 주는 실제 경로는 보존하면서 환경을 통제합니다.
가능하면 방문자를 대표하는 하드웨어를 사용합니다. CPU 제한은 민감도를 드러내지만 시뮬레이션이며 그렇게 기록해야 합니다. 실제로 해당 휴대전화를 시험하지 않았다면 로컬 제한 기록을 특정 휴대전화의 측정 결과로 표현하지 않습니다.
상호작용 대상, 조건, 관련 호출 스택을 저장합니다. 좋은 버그 보고서는 어떤 제어가 느리고 그 시간에 무슨 작업이 있는지 설명합니다. 빨간 집계 점수의 스크린샷만 있으면 다음 담당자가 조사를 반복해야 합니다.
집계 보고서가 설명하지 못하는 부분에 attribution을 추가합니다
CrUX와 Search Console은 집단 수준 문제를 찾을 수 있지만 어느 컴포넌트나 배포가 원인인지는 알려 주지 않을 수 있습니다. 자체 계측은 개인정보 보호와 브라우저 지원 범위에서 느린 상호작용을 애플리케이션 맥락에 연결합니다.
다음 진단 모듈은 호환 패키지가 설치된 프로젝트에서 web-vitals attribution 빌드를 사용합니다. 상호작용 정보를 서버로 보내지 않고 구간별 시간을 기록합니다.
import { onINP } from 'web-vitals/attribution';
onINP(({ value, attribution }) => {
console.table({
inp: value,
inputDelay: attribution.inputDelay,
processingDuration: attribution.processingDuration,
presentationDelay: attribution.presentationDelay
});
});설치된 버전의 attribution API를 확인합니다. 운영 수집에는 집계, 중복 제거, 기록할 맥락에 대한 정책이 필요합니다. 붙이기 쉽다는 이유로 입력 텍스트, 계정 식별자, 비공개 매개변수가 든 전체 URL을 수집하지 않습니다.
attribution은 재현할 단서로 봅니다. 데이터 범위가 CrUX와 다를 수 있고 하나의 집계값에 여러 종류의 느린 행동이 섞일 수 있습니다. 모든 느린 표본의 원인이 같다고 생각하지 말고 적절한 템플릿이나 컴포넌트별로 묶습니다.
입력 시작을 막는 작업을 제거합니다
메인 스레드가 다른 작업을 수행하면 입력 핸들러는 실행되지 못합니다. 50밀리초를 넘는 작업은 긴 작업으로 분류됩니다. 더 짧더라도 스케줄링과 렌더링이 누적되면 지연에 기여할 수 있습니다.
핸들러 직전 구간을 확인합니다. 시작 코드, 반복 타이머, 분석 작업, hydration, 외부 위젯이 흔한 후보입니다. 클릭한 대상 자체에는 문제가 없을 수 있습니다. 가벼운 메뉴도 관계없는 작업이 스레드를 점유하면 느려집니다.
복잡한 스케줄링에 앞서 미사용 기능과 중복 초기화를 제거합니다. 코드 분할은 해당 경로에 필요 없는 기능의 로딩과 평가를 피하게 합니다. 스크립트를 뒤로 미뤄도 실행 시점만 달라질 뿐 비용은 없어지지 않습니다.
필수적인 비싼 코드는 더 작은 작업이나 메인 스레드 밖에서 실행할 수 있는지 조사합니다. JavaScript 실행 시간 가이드가 이 선택을 설명합니다. 목적은 초기 로드 이벤트만이 아니라 실제 상호작용 주변의 경합을 줄이는 것입니다.
독립적인 작업 사이에 제어권을 양보합니다
큰 함수를 여러 작은 함수로 나누어도 브라우저 작업이 자동으로 나뉘지는 않습니다. 같은 핸들러에서 연속 호출하면 여전히 작업 종료까지 스레드가 점유됩니다.
scheduler.yield()는 나중의 작업에서 실행을 이어 갑니다. 지원 범위가 달라 기능 감지와 대체 수단이 필요합니다. 타이머 대체도 새 작업을 만들지만 이어지는 작업의 우선순위 동작까지 같지는 않습니다.
다음 완전한 브라우저 예시는 로컬 데이터를 예시 크기의 조각으로 처리합니다. 조각 크기는 기법 설명용이지 모든 환경의 성능 예산이 아닙니다.
<button id="process" type="button">Process example data</button>
<p id="progress" role="status">Ready</p>
<script type="module">
const button = document.querySelector('#process');
const progress = document.querySelector('#progress');
const values = Array.from({ length: 10000 }, (_, index) => index);
async function yieldToBrowser() {
if (globalThis.scheduler?.yield) {
await globalThis.scheduler.yield();
} else {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
button.addEventListener('click', async () => {
button.disabled = true;
progress.textContent = 'Processing';
try {
let total = 0;
for (let start = 0; start < values.length; start += 250) {
await yieldToBrowser();
const end = Math.min(start + 250, values.length);
for (let index = start; index < end; index++) {
total += values[index];
}
}
progress.textContent = `Total: ${total}`;
} finally {
button.disabled = false;
}
});
</script>양보는 브라우저가 다른 일을 할 기회를 줍니다. 특정 순간에 특정 프레임이 표시된다는 보장은 아닙니다. 대표 기기에서 실제 작업을 측정하고 조각당 작업량을 조절합니다.
원자적으로 끝나야 하는 상태 변경 중간에는 양보하지 않습니다. 이어서 실행하기 전에 다른 코드가 공유 상태를 바꿀 수 있습니다. 정확성을 유지하고 필요하면 취소를 처리하며 반복 활성화도 안전하게 만듭니다. 두 번 실행되는 빠른 인터페이스는 개선이 아닙니다.
requestIdleCallback은 정말 선택적인 백그라운드 작업에만 기능 감지와 적절한 대체 수단을 갖춰 사용합니다. 브라우저가 바쁘면 콜백이 기다리므로 즉각적인 피드백이나 반드시 완료해야 하는 작업을 대신하지 못합니다. timeout은 기다림을 제한하지만 그 경우 실행이 유휴 시간에만 한정되지 않습니다.
즉각적인 반응을 작고 유용하게 유지합니다
시각적 피드백 전에 필요하지 않은 작업이 핸들러에 있는지 봅니다. 선택 상태 갱신은 필수일 수 있습니다. 무관한 보고서 계산, 숨겨진 위젯 로딩, 부수적인 분석 전송은 그렇지 않을 수 있습니다.
실제 피드백을 잘못된 성공 표시로 바꾸지 않습니다. 실패할 수 있는 작업이라면 완료되었다고 하지 말고 진행 중임을 보여 줍니다. 키보드와 보조 기술 사용자도 동등한 정보를 받도록 오류 처리와 접근성 알림을 유지합니다.
worker는 큰 데이터 집합의 파싱이나 변환처럼 적절한 계산에 도움이 됩니다. 페이지 DOM을 직접 조작할 수 없고 통신 비용도 있습니다. 작은 연산을 옮기면 이익 없이 복잡해질 수 있습니다. 적절한 큰 작업을 옮기면 메인 스레드가 입력과 렌더링에 집중할 수 있습니다.
핸들러 실행 빈도도 확인합니다. 중복 리스너, 반복 프레임워크 효과, 불필요한 상태 전파는 작은 작업도 여러 배로 만듭니다. 지연으로 감추지 말고 중복을 제거합니다.
검색 제안처럼 반복 입력은 debounce로 중간 값을 모두 처리하지 않게 할 수 있습니다. 입력 자체의 반응성은 유지하고 실제 작업에 맞게 지연을 정하며 오래된 비동기 결과는 취소하거나 무시합니다. 모든 제어에 무조건 적용하거나 화면의 응답 확인을 임의의 타이머 뒤로 미루지 않습니다.
핸들러 이후 필요한 렌더링을 줄입니다
상호작용이 페이지의 큰 부분을 바꾸면 표시 지연이 가장 클 수 있습니다. 핸들러가 끝난 뒤 스타일 재계산, 레이아웃, 페인트를 봅니다. 수천 개 노드를 추가하는 짧은 함수도 상당한 브라우저 작업을 유발합니다.
관련된 최소 영역만 갱신합니다. 큰 목록은 탐색, 포커스, 접근 가능한 콘텐츠를 유지하는 페이지 나누기나 신중한 가상화를 고려합니다. 초기 DOM이 작아도 나중의 상호작용이 같은 병목을 한꺼번에 만들면 효과가 없습니다.
반복문에서 레이아웃 읽기와 쓰기를 번갈아 반복하지 않습니다. 스타일 변경 뒤 최신 기하정보를 읽으면 동기 레이아웃이 강제될 수 있습니다. 가능하면 측정부터 모으고 변경을 적용한 뒤 트레이스에서 강제 레이아웃 감소를 확인합니다.
특정 DOM 노드 수를 보편적인 통과 기준으로 보지 않습니다. 복잡도, 스타일 의존성, 갱신 면적이 비용을 바꿉니다. 외운 숫자 아래인지보다 실제 상호작용이 발생시킨 렌더링 작업을 근거로 삼습니다.
같은 행동 중에 외부 스크립트를 점검합니다
태그 관리자, 채팅, 실험, 광고 코드는 네트워크 요청이 끝난 뒤에도 상호작용 주변에서 실행될 수 있습니다. 네트워크만 보면 실행 비용을 놓칩니다.
통제된 테스트에서 의심 리소스를 잠시 차단하거나 특정 연동을 비활성화하고 행동을 반복합니다. 페이지가 정상 작동하며 원인이 분리되었는지 확인합니다. 의존성이 없어 기능이 깨진 기록은 거짓으로 빠르게 보일 수 있습니다.
각 연동에 담당자와 해당 시점에 실행할 이유를 정합니다. 사용자에게 도움이 된다면 선택적 임베드를 필요할 때 로드하되 첫 상호작용이 초기화 비용을 포함할 수 있음을 기억합니다. facade가 모든 비용을 활성화 클릭으로 옮기는 데 그쳐서는 안 됩니다.
한 번의 좋은 기록 이상으로 검증합니다
표준 Lighthouse 탐색 실행은 로딩을 측정하고 차단 지표로 TBT를 보고합니다. 현장 INP에 필요한 방문자 상호작용을 만들지는 않습니다. TBT를 INP로 환산하거나 Lighthouse 점수 개선을 느린 필터가 고쳐졌다는 증거로 설명하지 않습니다.
같은 조건에서 실제 행동을 반복합니다. 구간, 보이는 응답, 최종 결과, 오류 상태를 검사합니다. 배포 뒤에는 관련 기기 범주와 템플릿별로 현장 결과를 살펴봅니다.
성능 체크리스트에 변경과 검증 조건을 기록하고 Core Web Vitals 가이드에서 남은 로딩이나 안정성 문제를 이어서 조사합니다. 해당 시나리오를 회귀 검사에 남겨 이후 스크립트나 레이아웃 변경이 지연을 조용히 되돌리지 않게 합니다.