1. 소개
이 Codelab은 web-vitals
라이브러리를 사용하여 다음 페인트에 대한 상호작용 (INP)을 측정하는 방법을 알아보는 대화형 Codelab입니다.
기본 요건
- HTML 및 JavaScript 개발에 관한 지식
- 권장: web.dev INP 측정항목 문서 읽기
학습할 내용
- 페이지에
web-vitals
라이브러리를 추가하고 기여 분석 데이터를 사용하는 방법 - 기여 분석 데이터를 사용하여 INP 개선을 시작할 부분과 방법을 진단하세요.
필요한 항목
- GitHub의 코드를 클론하고 npm 명령어를 실행할 수 있는 컴퓨터
- 텍스트 편집기
- 모든 상호작용 측정을 위한 최신 버전의 Chrome입니다.
2. 설정
코드 가져오기 및 실행
코드는 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에서는 Performance 패널과 Console을 모두 사용합니다. 언제든지 DevTools 상단에 있는 탭에서 이들 모드 간에 전환할 수 있습니다.
- INP 문제는 휴대기기에서 가장 자주 발생하므로 모바일 디스플레이 에뮬레이션으로 전환하세요.
- 데스크톱이나 노트북에서 테스트하면 실제 휴대기기보다 훨씬 우수한 성능을 경험할 수 있습니다. 성능을 보다 현실적으로 살펴보려면 Performance 패널의 오른쪽 상단에 있는 톱니바퀴 아이콘을 누른 다음 CPU 4x 감속을 선택합니다.
4. 설치 web-vitals
web-vitals
는 사용자가 경험하는 웹 바이탈 측정항목을 측정하기 위한 JavaScript 라이브러리입니다. 이 라이브러리를 사용하여 이러한 값을 캡처한 다음, 느린 상호작용이 발생하는 시점과 위치를 파악할 목적으로 추후 분석을 위해 분석 엔드포인트에 비콘을 표시할 수 있습니다.
페이지에 라이브러리를 추가하는 몇 가지 방법이 있습니다. 자체 사이트에 라이브러리를 설치하는 방법은 종속 항목을 관리하는 방법, 빌드 프로세스, 기타 요소에 따라 다릅니다. 모든 옵션은 라이브러리의 문서를 확인하세요.
이 Codelab은 npm에서 설치하고 스크립트를 직접 로드하여 특정 빌드 프로세스에 몰입하지 않도록 합니다.
다음과 같은 두 가지 버전의 web-vitals
를 사용할 수 있습니다.
- 'standard' 빌드를 사용해야 페이지 로드 시 Core 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
는 더 상세한 보고를 위한 reportAllChanges
옵션을 제공합니다. 사용 설정된 경우 모든 상호작용이 보고되지는 않지만, 이전 상호작용보다 느린 상호작용이 있을 때마다 보고됩니다.
스크립트에 옵션을 추가하고 페이지와 다시 상호작용해 봅니다.
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log, {reportAllChanges: true});
</script>
이제 페이지를 새로고침하면 상호작용이 콘솔에 보고되어 가장 느린 상호작용이 있을 때마다 업데이트됩니다. 예를 들어 검색창에 입력한 다음 검색어를 삭제해 보세요.
5. 기여 분석에는 어떤 것이 있나요?
대부분의 사용자가 페이지에서 경험하게 될 첫 번째 상호작용인 쿠키 사용 동의 대화상자부터 시작하겠습니다.
대부분의 페이지에는 사용자가 쿠키를 허용하면 동기식으로 트리거되는 쿠키가 필요한 스크립트가 있어서 클릭이 느린 상호작용이 될 수 있습니다. 그렇게 하면 됩니다.
Yes(예)를 클릭하여 (데모) 쿠키를 수락하고 DevTools 콘솔에 로깅된 INP 데이터를 살펴보세요.
이 최상위 정보는 표준 및 Attribution 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
). 모두 동일한 애니메이션 프레임에서 실행되면 이 시간에 병합됩니다. 이 경우 처리 시간은 INP 시간의 상당 부분인 295.6밀리초가 걸립니다.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: [{...}]
}],
여기에는 스타일 지정에 소요된 시간을 구분하는 것과 같은 여러 유용한 값이 있습니다. Long Animation Frames 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과 함수가 정의된 위치의 문자 위치도 확인할 수 있습니다.
요점
이 기여 분석 데이터에서 이 사례에 대해 무엇을 파악할 수 있나요?
- (스크립트 속성 항목의
attribution.interactionTarget
및invoker
속성에서)button#confirm
요소를 클릭하여 상호작용이 트리거되었습니다. - 이벤트 리스너를 실행하는 데 주로 소요된 시간이 총 측정항목
value
대비attribution.processingDuration
에서 발생했습니다. - 느린 이벤트 리스너 코드는
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 제한이 사용 설정된 경우)입니다. 대부분의 시간(1,072밀리초 총 INP 중 1,061밀리초)이 처리 시간에 소요되었습니다.
하지만 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
임)인 이 스크립트 실행 중에 발생합니다. 함수 이름은 index.js
소스 파일 내의 문자 위치와 마찬가지로 (handleSearch
)로 지정됩니다.
하지만 새로운 속성인 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);