웹사이트의 필드 데이터에서 느린 상호작용을 찾아 다음 페인트와의 상호작용을 개선할 기회를 찾는 방법을 알아보세요.
필드 데이터는 실제 사용자가 웹사이트를 어떻게 이용하고 있는지 알려주는 데이터입니다. 실험실 데이터만으로는 찾을 수 없는 문제를 파악할 수 있습니다. 다음 페인트에 대한 상호작용 (INP)의 경우 느린 상호작용을 식별하는 데 필드 데이터가 필요하며, 이를 수정하는 데 도움이 되는 중요한 단서를 제공합니다.
이 가이드에서는 Chrome 사용자 환경 보고서 (CrUX)의 필드 데이터를 사용하여 웹사이트의 INP를 빠르게 평가하고 웹사이트에 INP 문제가 있는지 확인하는 방법을 알아봅니다. 그런 다음 웹 속도 측정 JavaScript 라이브러리의 기여 분석 빌드와 Long Animation Frames API (LoAF)에서 제공하는 새로운 통계를 사용하여 웹사이트의 느린 상호작용에 관한 현장 데이터를 수집하고 해석하는 방법을 알아봅니다.
CrUX로 시작하여 웹사이트의 INP 평가
웹사이트 사용자로부터 필드 데이터를 수집하지 않는 경우 CrUX를 시작으로 하는 것이 좋습니다. CrUX는 원격 분석 데이터 전송을 선택한 실제 Chrome 사용자로부터 필드 데이터를 수집합니다.
CrUX 데이터는 여러 영역에 표시되며, 찾고 있는 정보의 범위에 따라 다릅니다. CrUX는 다음에 대한 INP 및 기타 Core Web Vitals에 관한 데이터를 제공할 수 있습니다.
- PageSpeed Insights를 사용하여 개별 페이지 및 전체 출처
- 페이지 유형 예를 들어 많은 전자상거래 웹사이트에는 제품 세부정보 페이지와 제품 등록정보 페이지 유형이 있습니다. Search Console에서 고유한 페이지 유형의 CrUX 데이터를 가져올 수 있습니다.
시작하려면 PageSpeed Insights에 웹사이트 URL을 입력합니다. URL을 입력하면 INP를 비롯한 여러 측정항목에 대해 해당 URL의 필드 데이터(있는 경우)가 표시됩니다. 전환 버튼을 사용하여 모바일 및 데스크톱 측정기준의 INP 값을 확인할 수도 있습니다.
이 데이터는 문제가 있는지 알려주므로 유용합니다. 하지만 CrUX는 문제를 일으키는 원인을 알려주지 못합니다. 웹사이트 사용자로부터 자체 필드 데이터를 수집하여 이 질문에 답하는 데 도움이 되는 여러 실시간 사용자 모니터링 (RUM) 솔루션이 있습니다. 그중 하나는 web-vitals JavaScript 라이브러리를 사용하여 필드 데이터를 직접 수집하는 것입니다.
web-vitals
JavaScript 라이브러리로 현장 데이터 수집
web-vitals
JavaScript 라이브러리는 웹사이트에 로드하여 웹사이트 사용자로부터 현장 데이터를 수집할 수 있는 스크립트입니다. 이를 사용하여 INP를 지원하는 브라우저에서 INP를 비롯한 여러 측정항목을 기록할 수 있습니다.
web-vitals 라이브러리의 표준 빌드를 사용하여 현장 사용자로부터 기본 INP 데이터를 가져올 수 있습니다.
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
console.log(name); // 'INP'
console.log(value); // 512
console.log(rating); // 'poor'
});
사용자의 현장 데이터를 분석하려면 다음과 같이 데이터를 전송해야 합니다.
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
// Prepare JSON to be sent for collection. Note that
// you can add anything else you'd want to collect here:
const body = JSON.stringify({name, value, rating});
// Use `sendBeacon` to send data to an analytics endpoint.
// For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
navigator.sendBeacon('/analytics', body);
});
하지만 이 데이터만으로는 CrUX에서 제공하는 것보다 많은 정보를 얻을 수 없습니다. 이때 웹-비탈스 라이브러리의 기여 분석 빌드가 사용됩니다.
웹 바이탈 라이브러리의 기여 분석 빌드로 더 나아가기
web-vitals 라이브러리의 기여 분석 빌드는 현장 사용자로부터 얻을 수 있는 추가 데이터를 표시하여 웹사이트의 INP에 영향을 미치는 문제가 있는 상호작용을 더 효과적으로 해결하는 데 도움이 됩니다. 이 데이터는 라이브러리의 onINP()
메서드에 표시된 attribution
객체를 통해 액세스할 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, rating, attribution}) => {
console.log(name); // 'INP'
console.log(value); // 56
console.log(rating); // 'good'
console.log(attribution); // Attribution data object
});
기여 분석 빌드는 페이지의 INP 자체 외에도 상호작용 속도가 느린 이유를 파악하는 데 사용할 수 있는 많은 데이터를 제공합니다(예: 상호작용 중 어느 부분에 중점을 둘지). 이를 통해 다음과 같은 중요한 질문에 답하는 데 도움이 될 수 있습니다.
- '사용자가 페이지가 로드되는 동안 페이지와 상호작용했나요?'
- '상호작용의 이벤트 핸들러가 오랫동안 실행되었나요?'
- "상호작용 이벤트 핸들러 코드가 시작이 지연되었나요? 그렇다면 그때 기본 스레드에서 어떤 일이 또 있었나요?"
- '상호작용으로 인해 다음 프레임의 페인팅이 지연될 만큼 많은 렌더링 작업이 발생했나요?'
다음 표에는 라이브러리에서 가져와 웹사이트의 느린 상호작용의 대략적인 원인을 파악하는 데 도움이 되는 몇 가지 기본 기여 분석 데이터가 나와 있습니다.
attribution 객체 키
|
데이터 |
---|---|
interactionTarget
|
페이지의 INP 값을 생성한 요소를 가리키는 CSS 선택자입니다(예: button#save ).
|
interactionType
|
클릭, 탭 또는 키보드 입력의 상호작용 유형입니다. |
inputDelay *
|
상호작용의 입력 지연입니다. |
processingDuration *
|
사용자 상호작용에 대한 응답으로 첫 번째 이벤트 리스너가 실행되기 시작한 시점부터 모든 이벤트 리스너 처리가 완료될 때까지의 시간입니다. |
presentationDelay *
|
상호작용의 표시 지연으로, 이벤트 핸들러가 완료된 시점부터 다음 프레임이 페인트될 때까지 발생합니다. |
longAnimationFrameEntries *
|
상호작용과 연결된 LoAF의 항목입니다. 자세한 내용은 다음을 참고하세요. |
웹-비탈스 라이브러리 버전 4부터 INP 단계 분석 (입력 지연, 처리 시간, 프레젠테이션 지연) 및 Long Animation Frames API (LoAF)와 함께 제공되는 데이터를 통해 문제가 있는 상호작용에 대한 심층적인 통계를 얻을 수 있습니다.
Long Animation Frames API (LoAF)
필드 데이터를 사용하여 상호작용을 디버그하는 것은 까다로운 작업입니다. 하지만 이제 LoAF의 데이터를 사용하면 느린 상호작용의 원인을 더 정확하게 파악할 수 있습니다. LoAF는 정확한 원인과 더 중요한 것은 웹사이트 코드에서 문제의 근원이 어디에 있는지 파악하는 데 사용할 수 있는 다양한 세부 타이밍 및 기타 데이터를 노출합니다.
web-vitals 라이브러리의 기여 분석 빌드는 attribution
객체의 longAnimationFrameEntries
키 아래에 LoAF 항목 배열을 노출합니다. 다음 표에는 각 LoAF 항목에서 찾을 수 있는 몇 가지 주요 데이터가 나와 있습니다.
LoAF 항목 객체 키 | 데이터 |
---|---|
duration
|
긴 애니메이션 프레임의 기간으로, 레이아웃이 완료될 때까지의 기간이며 페인팅 및 합성은 제외됩니다. |
blockingDuration
|
긴 작업으로 인해 브라우저가 빠르게 응답할 수 없었던 프레임의 총 시간입니다. 이 차단 시간에는 JavaScript를 실행하는 긴 태스크와 프레임의 후속 긴 렌더링 태스크가 포함될 수 있습니다. |
firstUIEventTimestamp
|
프레임 중에 이벤트가 큐에 추가된 시점의 타임스탬프입니다. 상호작용의 입력 지연 시작을 파악하는 데 유용합니다. |
startTime
|
프레임의 시작 타임스탬프입니다. |
renderStart
|
프레임 렌더링 작업이 시작된 시간입니다. 여기에는 모든 requestAnimationFrame 콜백 (및 해당하는 경우 ResizeObserver 콜백)이 포함되며, 스타일/레이아웃 작업이 시작되기 전에 실행될 수 있습니다.
|
styleAndLayoutStart
|
프레임에서 스타일/레이아웃 작업이 발생할 때 사용 가능한 다른 타임스탬프를 고려할 때 스타일/레이아웃 작업의 길이를 파악하는 데 유용할 수 있습니다. |
scripts
|
페이지의 INP에 기여하는 스크립트 기여 분석 정보가 포함된 항목 배열입니다. |
이 모든 정보는 상호작용이 느려지는 원인에 대해 많은 정보를 제공할 수 있지만 LoAF 항목이 표시하는 scripts
배열에 특히 관심을 가져야 합니다.
스크립트 기여 분석 객체 키 | 데이터 |
---|---|
invoker
|
호출자입니다. 이는 다음 행에 설명된 호출자 유형에 따라 다를 수 있습니다. 호출자의 예로는 'IMG#id.onload' , 'Window.requestAnimationFrame' , 'Response.json.then' 과 같은 값이 있습니다. |
invokerType
|
호출자의 유형입니다. 'user-callback' , 'event-listener' , 'resolve-promise' , 'reject-promise' , 'classic-script' , 또는 'module-script' 일 수 있습니다.
|
sourceURL
|
긴 애니메이션 프레임이 시작된 스크립트의 URL입니다. |
sourceCharPosition
|
sourceURL 로 식별되는 스크립트의 문자 위치입니다.
|
sourceFunctionName
|
식별된 스크립트의 함수 이름입니다. |
이 배열의 각 항목에는 이 표에 표시된 데이터가 포함되어 있으며, 이 데이터는 느린 상호작용을 일으킨 스크립트와 그 원인에 관한 정보를 제공합니다.
느린 상호작용의 일반적인 원인을 측정하고 파악
이 정보를 사용하는 방법을 알아보려면 이 가이드에서 web-vitals
라이브러리에 표시된 LoAF 데이터를 사용하여 느린 상호작용의 원인을 파악하는 방법을 알아보세요.
긴 처리 시간
상호작용의 처리 시간은 상호작용의 등록된 이벤트 핸들러 콜백이 실행을 완료하는 데 걸리는 시간과 그 사이에서 발생할 수 있는 다른 모든 작업에 걸리는 시간입니다. 처리 시간이 긴 경우 web-vitals 라이브러리에 표시됩니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
});
상호작용이 느린 주된 원인은 이벤트 핸들러 코드가 실행되는 데 시간이 너무 오래 걸리기 때문이라고 생각하는 것이 자연스럽지만, 항상 그런 것은 아닙니다. 이 문제가 원인임을 확인한 후 LoAF 데이터를 자세히 살펴볼 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
// Get the longest script from LoAF covering `processingDuration`:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Get attribution for the long-running event handler:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
위의 코드 스니펫에서 볼 수 있듯이 LoAF 데이터를 사용하여 처리 시간이 긴 상호작용의 정확한 원인을 파악할 수 있습니다. 여기에는 다음이 포함됩니다.
- 요소와 등록된 이벤트 리스너입니다.
- 장기 실행 이벤트 핸들러 코드가 포함된 스크립트 파일 및 그 내의 문자 위치입니다.
- 함수 이름입니다.
이러한 유형의 데이터는 매우 중요합니다. 더 이상 처리 시간이 긴 상호작용 또는 이벤트 핸들러를 정확히 찾기 위해 수고할 필요가 없습니다. 또한 서드 파티 스크립트는 자체 이벤트 핸들러를 등록하는 경우가 많으므로 내 코드가 원인인지 여부를 확인할 수 있습니다. 제어할 수 있는 코드의 경우 긴 작업 최적화를 살펴보세요.
긴 입력 지연
장기 실행 이벤트 핸들러는 일반적이지만 고려해야 할 다른 상호작용 부분도 있습니다. 한 부분은 처리 시간 전에 발생하며 이를 입력 지연이라고 합니다. 사용자가 상호작용을 시작하는 시점부터 이벤트 핸들러 콜백이 실행되기 시작하는 시점까지의 시간으로, 기본 스레드가 이미 다른 작업을 처리하고 있을 때 발생합니다. 웹-비탈스 라이브러리의 기여 분석 빌드를 사용하면 상호작용의 입력 지연 시간을 알 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
});
일부 상호작용의 입력 지연 시간이 길면 상호작용 시 페이지에서 어떤 일이 발생하여 입력 지연 시간이 길어졌는지 파악해야 합니다. 이는 페이지 로드 시 상호작용이 발생했는지 아니면 그 이후에 발생했는지에 따라 달라집니다.
페이지 로드 중에 발생했나요?
페이지가 로드될 때 기본 스레드가 가장 바쁜 경우가 많습니다. 이 기간에는 모든 종류의 작업이 대기열에 추가되고 처리되며, 이러한 작업이 모두 실행되는 동안 사용자가 페이지와 상호작용하려고 하면 상호작용이 지연될 수 있습니다. JavaScript를 많이 로드하는 페이지는 스크립트를 컴파일하고 평가하는 작업을 시작할 수 있으며, 사용자 상호작용에 대비하여 페이지를 준비하는 함수를 실행할 수도 있습니다. 이 활동이 발생하는 동안 사용자가 상호작용하는 경우 이 작업이 방해가 될 수 있으며 웹사이트 사용자에게 이러한 문제가 있는지 확인할 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Invoker types can describe if script eval blocked the main thread:
const {invokerType} = script; // 'classic-script' | 'module-script'
const {sourceLocation} = script; // 'https://example.com/app.js'
}
});
이 데이터를 필드에 기록했는데 입력 지연 시간이 길고 호출자 유형이 'classic-script'
또는 'module-script'
인 경우 사이트의 스크립트가 평가하는 데 시간이 오래 걸리고 상호작용을 지연시킬 만큼 충분히 오랫동안 기본 스레드를 차단하고 있다고 말할 수 있습니다. 스크립트를 더 작은 번들로 나누고, 처음에는 사용되지 않는 코드를 나중에 로드되도록 지연시키고, 사이트에서 완전히 삭제할 수 있는 사용되지 않는 코드를 감사하여 이 차단 시간을 줄일 수 있습니다.
페이지 로드 후였나요?
입력 지연은 페이지가 로드되는 동안 자주 발생하지만, 완전히 다른 원인으로 인해 페이지가 로드된 후에도 발생할 수 있습니다. 페이지 로드 후 입력 지연의 일반적인 원인은 이전 setInterval
호출로 인해 주기적으로 실행되는 코드 또는 이전에 실행되도록 대기열에 추가되었으며 아직 처리 중인 이벤트 콜백일 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
const {invokerType} = script; // 'user-callback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
처리 시간이 긴 경우와 마찬가지로 앞서 언급한 원인으로 인해 입력 지연이 발생하면 자세한 스크립트 기여 분석 데이터를 확인할 수 있습니다. 그러나 상호작용을 지연시킨 작업의 성격에 따라 호출자 유형이 달라집니다.
'user-callback'
는 차단 태스크가setInterval
,setTimeout
또는requestAnimationFrame
에서 발생했음을 나타냅니다.'event-listener'
은 차단 태스크가 대기열에 추가되어 아직 처리 중인 이전 입력에서 비롯되었음을 나타냅니다.'resolve-promise'
및'reject-promise'
은 차단 태스크가 이전에 시작된 비동기 작업에서 발생했으며 사용자가 페이지와 상호작용하려고 시도할 때 해결되거나 거부되어 상호작용이 지연되었음을 의미합니다.
어쨌든 스크립트 기여 분석 데이터를 통해 어디에서부터 살펴볼지, 입력 지연이 자체 코드로 인한 것인지 아니면 서드 파티 스크립트로 인한 것인지 파악할 수 있습니다.
긴 프레젠테이션 지연
프레젠테이션 지연은 상호작용의 마지막 단계이며 상호작용의 이벤트 핸들러가 완료된 시점부터 다음 프레임이 페인트될 때까지 진행됩니다. 상호작용으로 인해 이벤트 핸들러의 작업이 사용자 인터페이스의 시각적 상태를 변경할 때 발생합니다. 처리 시간 및 입력 지연과 마찬가지로 웹-비탈스 라이브러리는 상호작용의 표시 지연 시간을 알려줍니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
});
이 데이터를 기록할 때 웹사이트의 INP에 기여하는 상호작용의 표시 지연이 심한 경우 원인은 다양할 수 있지만 다음과 같은 몇 가지 원인을 주의 깊게 살펴보세요.
비용이 많이 드는 스타일 및 레이아웃 작업
프레젠테이션이 오래 지연되는 것은 복잡한 CSS 선택기와 큰 DOM 크기 등 여러 원인으로 인해 발생하는 비용이 많이 드는 스타일 재계산 및 레이아웃 작업 때문일 수 있습니다. 웹-비탈스 라이브러리에 표시된 LoAF 타이밍을 사용하여 이 작업의 시간을 측정할 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get necessary timings:
const {startTime} = loaf; // 2120.5
const {duration} = loaf; // 1002
// Figure out the ending timestamp of the frame (approximate):
const endTime = startTime + duration; // 3122.5
// Get the start timestamp of the frame's style/layout work:
const {styleAndLayoutStart} = loaf; // 3011.17692309
// Calculate the total style/layout duration:
const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691
if (script) {
// Get attribution for the event handler that triggered
// the long-running style and layout operation:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
LoAF는 프레임의 스타일 및 레이아웃 작업 기간을 알려주지 않지만 시작 시점은 알려줍니다. 이 시작 타임스탬프를 사용하면 LoAF의 다른 데이터를 사용하여 프레임의 종료 시간을 확인하고 스타일 및 레이아웃 작업의 시작 타임스탬프를 빼서 해당 작업의 정확한 시간을 계산할 수 있습니다.
장기 실행 requestAnimationFrame
콜백
프레젠테이션이 오래 지연되는 한 가지 원인은 requestAnimationFrame
콜백에서 과도한 작업이 실행되기 때문입니다. 이 콜백의 콘텐츠는 이벤트 핸들러의 실행이 완료된 후 스타일 재계산 및 레이아웃 작업 직전에 실행됩니다.
이러한 콜백은 내부에서 실행되는 작업이 복잡한 경우 완료하는 데 상당한 시간이 걸릴 수 있습니다. requestAnimationFrame
로 수행하는 작업으로 인해 프레젠테이션 지연 시간이 길어지는 것으로 의심되는 경우 웹-비탈스 라이브러리에서 표시되는 LoAF 데이터를 사용하여 다음 시나리오를 식별할 수 있습니다.
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 543.1999999880791
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get the render start time and when style and layout began:
const {renderStart} = loaf; // 2489
const {styleAndLayoutStart} = loaf; // 2989.5999999940395
// Calculate the `requestAnimationFrame` callback's duration:
const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954
if (script) {
// Get attribution for the event handler that triggered
// the long-running requestAnimationFrame callback:
const {invokerType} = script; // 'user-callback'
const {invoker} = script; // 'FrameRequestCallback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
프레젠테이션 지연 시간의 상당 부분이 requestAnimationFrame
콜백에서 소비되는 경우 이러한 콜백에서 실행하는 작업이 사용자 인터페이스의 실제 업데이트로 이어지는 작업 실행으로 제한되는지 확인합니다. DOM을 건드리거나 스타일을 업데이트하지 않는 다른 작업은 다음 프레임이 페인트되는 것을 불필요하게 지연하므로 주의하세요.
결론
현장 데이터는 현장의 실제 사용자에게 문제가 되는 상호작용이 무엇인지 파악하는 데 활용할 수 있는 최고의 정보 소스입니다. 웹-비탈스 JavaScript 라이브러리 (또는 RUM 제공업체)와 같은 현장 데이터 수집 도구를 사용하면 가장 문제가 되는 상호작용을 더 확실하게 파악한 후 실험실에서 문제가 되는 상호작용을 재현하여 문제를 해결할 수 있습니다.
Unsplash의 히어로 이미지, Federico Respini 제공.