1. 소개
이 Codelab은 다음 페인트에 대한 상호작용 (INP)을 측정하는 방법을 학습하는 대화형 Codelab입니다. web-vitals 라이브러리를 사용하여
기본 요건
- HTML 및 JavaScript 개발에 관한 지식
- 권장사항: web.dev INP 측정항목 문서 읽기
학습할 내용
- 페이지에
web-vitals라이브러리를 추가하고 기여 분석 데이터를 사용하는 방법 - 기여 분석 데이터를 사용하여 INP 개선을 시작할 위치와 방법을 진단합니다.
필요한 항목
- GitHub에서 코드를 클론하고 npm 명령어를 실행할 수 있는 컴퓨터
- 텍스트 편집기
- 모든 상호작용 측정항목이 작동하려면 최신 버전의 Chrome이 필요합니다.
2. 설정
코드 가져오기 및 실행
코드는 the web-vitals-codelabs 저장소에 있습니다.
- 터미널에서 저장소를 클론합니다.
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git - 클론된 디렉터리로 이동합니다.
cd web-vitals-codelabs/measuring-inp - 종속 항목을 설치합니다.
npm ci - 웹 서버를 시작합니다.
npm run start - 브라우저에서 http://localhost:8080/을 방문합니다.
페이지 사용해 보기
이 Codelab에서는 Gastropodicon (인기 있는 달팽이 해부학 참고 사이트)을 사용하여 INP의 잠재적 문제를 살펴봅니다.

페이지와 상호작용하여 느린 상호작용을 파악해 보세요.
3. Chrome DevTools에서 위치 지정
DevTools는 도구 더보기 > 개발자 도구 메뉴에서 열거나, 페이지를 마우스 오른쪽 버튼으로 클릭하고 검사를 선택하거나, 키보드 단축키를 사용하여 엽니다.
이 Codelab에서는 실적 패널과 콘솔 을 모두 사용합니다. 언제든지 DevTools 상단의 탭에서 전환할 수 있습니다.
- INP 문제는 모바일 기기에서 가장 자주 발생하므로 모바일 디스플레이 에뮬레이션으로 전환합니다.
- 데스크톱 또는 노트북에서 테스트하는 경우 실제 모바일 기기보다 성능이 훨씬 더 좋을 수 있습니다. 더 현실적인 성능을 보려면 실적 패널의 오른쪽 상단에 있는 톱니바퀴를 클릭한 다음 CPU 4배 속도 저하 를 선택합니다.

4. web-vitals 설치
web-vitals 는 사용자가 경험하는 웹 바이탈 측정항목을 측정하기 위한 JavaScript 라이브러리입니다. 라이브러리를 사용하여 이러한 값을 캡처한 다음 나중에 분석할 수 있도록 분석 엔드포인트로 비콘을 전송할 수 있습니다. 이 Codelab에서는 느린 상호작용이 발생하는 시점과 위치를 파악하는 데 사용합니다.
페이지에 라이브러리를 추가하는 방법은 여러 가지가 있습니다. 자체 사이트에 라이브러리를 설치하는 방법은 종속 항목, 빌드 프로세스, 기타 요소를 관리하는 방법에 따라 다릅니다. 모든 옵션은 라이브러리 문서를 확인하세요.
이 Codelab에서는 특정 빌드 프로세스를 자세히 살펴보지 않도록 npm에서 설치하고 스크립트를 직접 로드합니다.
사용할 수 있는 web-vitals 버전은 두 가지입니다.
- 페이지 로드 시 코어 웹 바이탈의 측정항목 값을 추적하려면 '표준' 빌드를 사용해야 합니다.
- '기여 분석' 빌드는 각 측정항목에 추가 디버그 정보를 추가하여 측정항목이 특정 값을 갖게 되는 이유를 진단합니다.
이 Codelab에서 INP를 측정하려면 기여 분석 빌드가 필요합니다.
npm install -D web-vitals를 실행하여 프로젝트의 devDependencies에 web-vitals를 추가합니다.
페이지에 web-vitals 추가
index.html 하단에 스크립트의 기여 분석 버전을 추가하고 결과를 콘솔에 로깅합니다.
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log);
</script>
사용해 보기
콘솔을 열고 페이지와 다시 상호작용해 보세요. 페이지를 클릭해도 아무것도 로깅되지 않습니다.
INP는 페이지의 전체 수명 주기 동안 측정되므로 기본적으로 web-vitals는 사용자가 페이지를 나가거나 닫을 때까지 INP를 보고하지 않습니다. 이는 분석과 같은 항목의 비콘 전송에 이상적인 동작이지만 대화형 디버깅에는 적합하지 않습니다.
web-vitals는 더 자세한 보고를 위해 the reportAllChanges 옵션을 제공합니다. 사용 설정하면 모든 상호작용이 보고되는 것은 아니지만 이전 상호작용보다 느린 상호작용이 있을 때마다 보고됩니다.
스크립트에 옵션을 추가하고 페이지와 다시 상호작용해 보세요.
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log, {reportAllChanges: true});
</script>
페이지를 새로고침하면 이제 상호작용이 콘솔에 보고되고 가장 느린 상호작용이 있을 때마다 업데이트됩니다. 예를 들어 검색창에 입력한 다음 입력을 삭제해 보세요.

5. 기여 분석에 포함된 항목
대부분의 사용자가 페이지와 처음 상호작용하는 쿠키 동의 대화상자부터 시작해 보겠습니다.
많은 페이지에는 사용자가 쿠키를 수락할 때 동기식으로 트리거해야 하는 쿠키가 필요한 스크립트가 있어 클릭이 느린 상호작용이 됩니다. 이 경우에도 마찬가지입니다.
예 를 클릭하여 (데모) 쿠키를 수락하고 이제 DevTools 콘솔에 로깅된 INP 데이터를 확인합니다.

이 최상위 정보는 표준 및 기여 분석 web-vitals 빌드 모두에서 사용할 수 있습니다.
{
name: 'INP',
value: 344,
rating: 'needs-improvement',
entries: [...],
id: 'v4-1715732159298-8028729544485',
navigationType: 'reload',
attribution: {...},
}
사용자가 클릭한 시점부터 다음 페인트까지의 시간은 344밀리초로 "개선 필요" INP입니다. entries 배열에는 이 상호작용과 연결된 모든 PerformanceEntry 값이 있습니다. 이 경우 클릭 이벤트가 하나만 있습니다.
하지만 이 시간 동안 어떤 일이 발생하는지 알아보려면 attribution 속성에 가장 관심이 있습니다. 기여 분석 데이터를 빌드하기 위해 web-vitals는 클릭 이벤트와 겹치는 긴 애니메이션 프레임 (LoAF)을 찾습니다. 그러면 LoAF는 실행된 스크립트부터 requestAnimationFrame 콜백, 스타일, 레이아웃에 소요된 시간까지 해당 프레임 동안 소요된 시간에 관한 세부 데이터를 제공할 수 있습니다.
attribution 속성을 펼쳐 자세한 내용을 확인합니다. 데이터가 훨씬 더 풍부합니다.
attribution: {
interactionTargetElement: Element,
interactionTarget: '#confirm',
interactionType: 'pointer',
inputDelay: 27,
processingDuration: 295.6,
presentationDelay: 21.4,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
먼저 상호작용한 항목에 관한 정보가 있습니다.
interactionTargetElement: 상호작용한 요소에 대한 실시간 참조입니다 (요소가 DOM에서 삭제되지 않은 경우).interactionTarget: 페이지 내에서 요소를 찾는 선택기입니다.
다음으로 타이밍이 대략적으로 분류됩니다.
inputDelay: 사용자가 상호작용을 시작한 시점 (예: 마우스를 클릭한 시점)부터 해당 상호작용의 이벤트 리스너가 실행되기 시작한 시점까지의 시간입니다. 이 경우 CPU 스로틀링이 사용 설정되어 있어도 입력 지연은 약 27밀리초에 불과했습니다.processingDuration: 이벤트 리스너가 완료될 때까지 실행되는 데 걸리는 시간입니다. 일반적으로 페이지에는 단일 이벤트 (예:pointerdown,pointerup,click)에 여러 리스너가 있습니다. 모두 동일한 애니메이션 프레임에서 실행되면 이 시간으로 통합됩니다. 이 경우 처리 기간은 295.6밀리초로 INP 시간의 대부분을 차지합니다.presentationDelay: 이벤트 리스너가 완료된 시점부터 브라우저가 다음 프레임 페인팅을 완료할 때까지의 시간입니다. 이 경우 21.4밀리초입니다.
이러한 INP 단계는 최적화해야 할 항목을 진단하는 데 중요한 신호가 될 수 있습니다. INP 최적화 가이드에서 이 주제에 관한 자세한 내용을 확인할 수 있습니다.
좀 더 자세히 살펴보면 processedEventEntries에는 최상위 INP entries 배열의 단일 이벤트와 달리 5개 이벤트가 포함되어 있습니다. 어떤 차이가 있나요?
processedEventEntries: [
{
name: 'mouseover',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{
name: 'mousedown',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{name: 'mousedown', ...},
{name: 'mouseup', ...},
{name: 'click', ...},
],
최상위 항목은 INP 이벤트입니다. 이 경우 클릭입니다. 기여 분석 processedEventEntries는 동일한 프레임에서 처리된 모든 이벤트입니다. 클릭 이벤트뿐만 아니라 mouseover, mousedown과 같은 다른 이벤트도 포함되어 있습니다. 이러한 다른 이벤트도 느린 경우 느린 반응성에 모두 기여했으므로 이러한 이벤트를 아는 것이 중요할 수 있습니다.
마지막으로 longAnimationFrameEntries 배열이 있습니다. 단일 항목일 수 있지만 상호작용이 여러 프레임에 걸쳐 확산될 수 있는 경우도 있습니다. 여기에는 단일 긴 애니메이션 프레임이 있는 가장 간단한 사례가 있습니다.
longAnimationFrameEntries
LoAF 항목 펼치기:
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 1823,
duration: 319,
renderStart: 2139.5,
styleAndLayoutStart: 2139.7,
firstUIEventTimestamp: 1801.6,
blockingDuration: 268,
scripts: [{...}]
}],
여기에는 스타일 지정에 소요된 시간을 분류하는 등 유용한 값이 많이 있습니다. 긴 애니메이션 프레임 API 도움말에서 이러한 속성을 자세히 설명합니다. 지금은 주로 장기 실행 프레임을 담당하는 스크립트에 관한 세부정보를 제공하는 항목이 포함된 scripts 속성에 관심이 있습니다.
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 1828.6,
executionStart: 1828.6,
duration: 294,
sourceURL: 'http://localhost:8080/third-party/cmp.js',
sourceFunctionName: '',
sourceCharPosition: 1144
}]
이 경우 BUTTON#confirm.onclick에서 호출된 단일 event-listener에 주로 시간이 소요되었음을 알 수 있습니다. 스크립트 소스 URL과 함수가 정의된 문자 위치도 확인할 수 있습니다.
요점 정리
이 기여 분석 데이터에서 이 사례에 관해 확인할 수 있는 내용은 무엇인가요?
- 상호작용은
button#confirm요소의 클릭으로 트리거되었습니다 (attribution.interactionTarget및 스크립트 기여 분석 항목의invoker속성에서). - 시간은 주로 이벤트 리스너를 실행하는 데 소요되었습니다 (
attribution.processingDuration을 총 측정항목value와 비교). - 느린 이벤트 리스너 코드는
third-party/cmp.js에 정의된 클릭 리스너에서 시작됩니다 (scripts.sourceURL에서).
최적화해야 할 위치를 파악하기에 충분한 데이터입니다.
6. 여러 이벤트 리스너
DevTools 콘솔이 지워지고 쿠키 동의 상호작용이 더 이상 가장 긴 상호작용이 아니도록 페이지를 새로고침합니다.
검색창에 입력을 시작합니다. 기여 분석 데이터에 표시되는 내용은 무엇인가요? 어떤 일이 일어나고 있다고 생각하시나요?
기여 분석 데이터
먼저 데모 테스트의 한 가지 예를 대략적으로 살펴보겠습니다.
{
name: 'INP',
value: 1072,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'keyboard',
inputDelay: 3.3,
processingDuration: 1060.6,
presentationDelay: 8.1,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
}
input#search-terms 요소와의 키보드 상호작용으로 인한 INP 값이 좋지 않습니다 (CPU 스로틀링 사용 설정). 시간의 대부분(총 INP 1072밀리초 중 1061밀리초)이 처리 기간에 소요되었습니다.
하지만 scripts 항목이 더 흥미롭습니다.
레이아웃 스래싱
scripts 배열의 첫 번째 항목은 몇 가지 유용한 컨텍스트를 제공합니다.
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 4875.6,
executionStart: 4875.6,
duration: 497,
forcedStyleAndLayoutDuration: 388,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'handleSearch',
sourceCharPosition: 940
},
...]
처리 기간의 대부분은 이 스크립트 실행 중에 발생합니다. 이는 input 리스너이며 호출자는 INPUT#search-terms.oninput입니다. 함수 이름(handleSearch)과 index.js 소스 파일 내의 문자 위치가 제공됩니다.
하지만 새 속성인 forcedStyleAndLayoutDuration이 있습니다. 이는 브라우저가 페이지를 다시 레이아웃하도록 강제된 이 스크립트 호출 내에서 소요된 시간입니다. 즉, 이 이벤트 리스너를 실행하는 데 소요된 시간의 78%(497밀리초 중 388밀리초)가 실제로 레이아웃 스래싱에 소요되었습니다.
이 문제를 해결하는 것이 최우선 과제여야 합니다.
반복되는 리스너
개별적으로 다음 두 스크립트 항목에는 특별히 주목할 만한 사항이 없습니다.
scripts: [...,
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5375.3,
executionStart: 5375.3,
duration: 124,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526,
},
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5673.9,
executionStart: 5673.9,
duration: 95,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526
}]
두 항목 모두 keyup 리스너이며 차례로 실행됩니다. 리스너는 익명 함수이므로 sourceFunctionName 속성에 아무것도 보고되지 않지만 소스 파일과 문자 위치가 있으므로 코드가 있는 위치를 찾을 수 있습니다.
이상한 점은 둘 다 동일한 소스 파일과 문자 위치에서 가져온 것 입니다.
브라우저가 단일 애니메이션 프레임에서 여러 키 누르기를 처리하여 페인팅되기 전에 이 이벤트 리스너가 두 번 실행되었습니다.
이 효과는 복합적일 수도 있습니다. 이벤트 리스너가 완료되는 데 걸리는 시간이 길수록 추가 입력 이벤트가 더 많이 발생하여 느린 상호작용이 훨씬 더 길어질 수 있습니다.
이것은 검색/자동 완성 상호작용이므로 프레임당 최대 하나의 키 누르기만 처리되도록 입력을 디바운스하는 것이 좋은 전략입니다.
7. 입력 지연
입력 지연(사용자가 상호작용하는 시점부터 이벤트 리스너가 상호작용 처리를 시작할 수 있는 시점까지의 시간)의 일반적인 이유는 기본 스레드가 사용 중이기 때문입니다. 이는 여러 가지 원인이 있을 수 있습니다.
- 페이지가 로드 중이고 기본 스레드가 DOM 설정, 페이지 레이아웃 및 스타일 지정, 스크립트 평가 및 실행의 초기 작업을 수행하는 데 사용 중입니다.
- 페이지가 일반적으로 사용 중입니다(예: 계산, 스크립트 기반 애니메이션 또는 광고 실행).
- 이전 상호작용을 처리하는 데 너무 오래 걸려 향후 상호작용이 지연됩니다. 이는 마지막 예에서 확인되었습니다.
데모 페이지에는 페이지 상단의 달팽이 로고를 클릭하면 애니메이션이 시작되고 기본 스레드 JavaScript 작업이 많이 실행되는 비밀 기능이 있습니다.
- 달팽이 로고를 클릭하여 애니메이션을 시작합니다.
- 달팽이가 바운스의 하단에 있을 때 JavaScript 작업이 트리거됩니다. 바운스의 하단에 최대한 가깝게 페이지와 상호작용하여 트리거할 수 있는 INP의 높이를 확인해 보세요.
예를 들어 달팽이가 튀어 오를 때 검색창을 클릭하고 포커스를 맞추는 것과 같은 다른 이벤트 리스너를 트리거하지 않더라도 기본 스레드 작업으로 인해 페이지가 상당한 시간 동안 응답하지 않게 됩니다.
많은 페이지에서 기본 스레드 작업이 이렇게 잘 작동하지는 않지만 INP 기여 분석 데이터에서 식별할 수 있는 방법을 보여주는 좋은 데모입니다.
다음은 달팽이 바운스 중에 검색창에만 포커스를 맞춘 기여 분석의 예입니다.
{
name: 'INP',
value: 728,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'pointer',
inputDelay: 702.3,
processingDuration: 4.9,
presentationDelay: 20.8,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 2064.8,
duration: 790,
renderStart: 2065,
styleAndLayoutStart: 2854.2,
firstUIEventTimestamp: 0,
blockingDuration: 740,
scripts: [{...}]
}]
}
}
예상대로 이벤트 리스너가 빠르게 실행되어 처리 기간이 4.9밀리초로 표시되었고, 좋지 않은 상호작용의 대부분은 입력 지연에 소요되어 총 728밀리초 중 702.3밀리초가 소요되었습니다.
이러한 상황은 디버깅하기 어려울 수 있습니다. 사용자가 상호작용한 항목과 방법을 알고 있지만 상호작용의 해당 부분이 빠르게 완료되었고 문제가 되지 않았다는 것도 알고 있습니다. 대신 페이지의 다른 항목으로 인해 상호작용 처리가 시작되지 않았지만 어디에서부터 찾아봐야 할까요?
LoAF 스크립트 항목이 문제를 해결합니다.
scripts: [{
name: 'script',
invoker: 'SPAN.onanimationiteration',
invokerType: 'event-listener',
startTime: 2065,
executionStart: 2065,
duration: 788,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'cryptodaphneCoinHandler',
sourceCharPosition: 1831
}]
이 함수는 상호작용과 관련이 없지만 애니메이션 프레임의 속도를 늦추므로 상호작용 이벤트와 결합된 LoAF 데이터에 포함됩니다.
여기에서 상호작용 처리를 지연시킨 함수가 트리거된 방법 (animationiteration 리스너)과 정확히 어떤 함수가 담당했는지, 소스 파일에서 어디에 있었는지 확인할 수 있습니다.
8. 프레젠테이션 지연: 업데이트가 페인팅되지 않는 경우
프레젠테이션 지연은 이벤트 리스너가 실행을 완료한 시점부터 브라우저가 새 프레임을 화면에 페인팅하여 사용자에게 표시되는 의견을 표시할 수 있는 시점까지의 시간을 측정합니다.
페이지를 새로고침하여 INP 값을 다시 재설정한 다음 햄버거 메뉴를 엽니다. 열릴 때 확실히 문제가 있습니다.
어떤 모습일까요?
{
name: 'INP',
value: 376,
rating: 'needs-improvement',
delta: 352,
attribution: {
interactionTarget: '#sidenav-button>svg',
interactionType: 'pointer',
inputDelay: 12.8,
processingDuration: 14.7,
presentationDelay: 348.5,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 651,
duration: 365,
renderStart: 673.2,
styleAndLayoutStart: 1004.3,
firstUIEventTimestamp: 138.6,
blockingDuration: 315,
scripts: [{...}]
}]
}
}
이번에는 느린 상호작용의 대부분을 차지하는 프레젠테이션 지연 입니다. 즉, 기본 스레드를 차단하는 항목은 이벤트 리스너가 완료된 후에 발생합니다.
scripts: [{
entryType: 'script',
invoker: 'FrameRequestCallback',
invokerType: 'user-callback',
startTime: 673.8,
executionStart: 673.8,
duration: 330,
sourceURL: 'http://localhost:8080/js/side-nav.js',
sourceFunctionName: '',
sourceCharPosition: 1193,
}]
scripts 배열의 단일 항목을 보면 시간이 FrameRequestCallback의 user-callback에 소요되었음을 알 수 있습니다. 이번에는 requestAnimationFrame 콜백으로 인해 프레젠테이션 지연이 발생합니다.
9. 결론
필드 데이터 집계
단일 페이지 로드에서 단일 INP 기여 분석 항목을 볼 때 이 모든 것이 더 쉽다는 점을 인식하는 것이 좋습니다. 이 데이터를 집계하여 필드 데이터를 기반으로 INP를 디버그하려면 어떻게 해야 할까요? 유용한 세부정보의 양이 실제로 더 어렵게 만듭니다.
예를 들어 느린 상호작용의 일반적인 소스인 페이지 요소를 아는 것이 매우 유용합니다. 하지만 페이지에 빌드마다 변경되는 컴파일된 CSS 클래스 이름이 있는 경우 동일한 요소의 web-vitals 선택기가 빌드마다 다를 수 있습니다.
대신 특정 애플리케이션을 고려하여 가장 유용한 항목과 데이터를 집계하는 방법을 결정해야 합니다. 예를 들어 기여 분석 데이터를 다시 비콘 전송하기 전에 대상이 있는 구성요소 또는 대상이 충족하는 ARIA 역할을 기반으로 web-vitals 선택기를 자체 식별자로 바꿀 수 있습니다.
마찬가지로 scripts 항목의 sourceURL 경로에 파일 기반 해시가 있어 결합하기 어려울 수 있지만 데이터를 다시 비콘 전송하기 전에 알려진 빌드 프로세스를 기반으로 해시를 삭제할 수 있습니다.
안타깝게도 이렇게 복잡한 데이터에는 쉬운 방법이 없지만 디버깅 프로세스에는 기여 분석 데이터가 전혀 없는 것보다 일부를 사용하는 것이 더 유용합니다.
모든 곳에서 기여 분석
LoAF 기반 INP 기여 분석은 강력한 디버깅 도구입니다. INP 중에 발생한 사항에 관한 세분화된 데이터를 제공합니다. 대부분의 경우 최적화 노력을 시작해야 하는 스크립트의 정확한 위치를 알려줄 수 있습니다.
이제 모든 사이트에서 INP 기여 분석 데이터를 사용할 수 있습니다.
페이지를 수정할 수 있는 액세스 권한이 없더라도 DevTools 콘솔에서 다음 스니펫을 실행하여 이 Codelab의 프로세스를 재현하고 찾을 수 있는 항목을 확인할 수 있습니다.
const script = document.createElement('script');
script.src = 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js';
script.onload = function () {
webVitals.onINP(console.log, {reportAllChanges: true});
};
document.head.appendChild(script);