웹사이트의 필드 데이터에서 느린 상호작용을 찾아 다음 페인트에 대한 상호작용을 개선할 기회를 찾는 방법을 알아봅니다.
필드 데이터는 실제 사용자가 내 웹사이트를 경험하는 방식을 보여주는 데이터입니다. 실험실 데이터만으로는 찾을 수 없는 문제를 미리 보여줍니다. 다음 페인트에 대한 상호작용 (INP)의 경우 필드 데이터는 느린 상호작용을 식별하는 데 필수적이며 이를 해결하는 데 도움이 되는 중요한 단서를 제공합니다.
이 가이드에서는 Chrome 사용자 환경 보고서 (CrUX)의 필드 데이터를 사용해 웹사이트의 INP를 빠르게 평가하여 웹사이트에 INP 문제가 있는지 확인하는 방법을 알아봅니다. 이어서 web-vitals 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 값을 확인할 수도 있습니다.
<ph type="x-smartling-placeholder">이 데이터는 문제가 있는지 알려주기 때문에 유용합니다. 그러나 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 라이브러리의 기여 분석 빌드가 필요합니다.
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의 항목 자세한 내용은 다음을 참고하세요. |
web-vitals 라이브러리 버전 4부터 INP 단계 분석 (입력 지연, 처리 기간, 프레젠테이션 지연) 및 Long Animation Frames API (LoAF)를 통해 제공되는 데이터를 통해 문제가 있는 상호작용에 대해 더 심층적인 통계를 얻을 수 있습니다.
Long Animation Frames API (LoAF)
필드 데이터를 사용하여 상호작용을 디버깅하는 것은 어려운 작업입니다. 그러나 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.sort((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 데이터를 사용하여 다음을 비롯하여 높은 처리 기간 값을 사용하는 상호작용의 정확한 원인을 추적할 수 있습니다.
- 요소 및 등록된 이벤트 리스너.
- 장기 실행 이벤트 핸들러 코드가 포함된 스크립트 파일과 그 안의 문자 위치
- 함수 이름입니다.
이러한 유형의 데이터는 매우 중요합니다. 이제 더 이상 높은 처리 기간 값을 담당한 상호작용 또는 해당 이벤트 핸들러가 무엇인지 알아내기 위해 많은 노력을 기울일 필요가 없습니다. 또한 서드 파티 스크립트가 자체 이벤트 핸들러를 등록할 수 있는 경우가 많기 때문에 이 때문에 코드가 온 것인지 확인할 수 있습니다. 제어할 수 있는 코드의 경우 장기 작업 최적화를 살펴보는 것이 좋습니다.
긴 입력 지연
장기 실행 이벤트 핸들러가 일반적이지만 고려해야 할 상호작용의 다른 부분도 있습니다. 한 부분은 처리 기간 전에 발생하는데, 이를 입력 지연이라고 합니다. 사용자가 상호작용을 시작한 시점부터 이벤트 핸들러 콜백이 실행되기 시작하고 기본 스레드에서 이미 다른 작업을 처리하고 있을 때까지의 시간입니다. web-vitals 라이브러리의 기여 분석 빌드는 상호작용의 입력 지연 길이를 알려줍니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
});
일부 상호작용의 입력 지연이 높은 경우, 긴 입력 지연을 유발한 상호작용 시점에 페이지에서 어떤 일이 발생했는지 파악해야 합니다. 이러한 상호작용은 페이지가 로드되는 동안 발생했는지 아니면 그 이후에 발생했는지로 요약됩니다.
페이지 로드 중에 발생했나요?
페이지가 로드될 때 기본 스레드가 가장 붐비는 경우가 많습니다. 이 시간 동안 모든 종류의 작업이 대기열에 추가되고 처리되며, 이 모든 작업이 진행되는 동안 사용자가 페이지와 상호작용하려고 하면 상호작용이 지연될 수 있습니다. 많은 자바스크립트를 로드하는 페이지는 스크립트를 컴파일하고 평가하는 것은 물론, 사용자와의 상호작용을 위해 페이지를 준비하는 함수도 실행하는 작업을 시작할 수 있습니다. 이 작업은 이 활동이 발생할 때 사용자가 상호 작용하는 경우 방해가 될 수 있으며 웹사이트 사용자에게도 이 경우에 해당하는지 확인할 수 있습니다.
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.sort((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.sort((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'
는 차단 작업이 앞서 시작된 일부 비동기 작업에서 발생했고 사용자가 페이지와 상호작용하려고 했을 때 해결되거나 거부되어 상호작용이 지연되었음을 의미합니다.
어떤 경우든 스크립트 기여 분석 데이터를 통해 어디서부터 시작해야 하는지, 입력 지연이 자체 코드에 의한 것인지, 서드 파티 스크립트로 인한 것인지 파악할 수 있습니다.
긴 프레젠테이션 지연
표시 지연은 상호작용의 마지막 마일이며, 상호작용의 이벤트 핸들러가 완료될 때, 다음 프레임이 페인트된 시점까지 시작됩니다. 상호작용으로 인해 이벤트 핸들러의 작업이 사용자 인터페이스의 시각적 상태를 변경할 때 발생합니다. 처리 기간 및 입력 지연과 마찬가지로 web-vitals 라이브러리는 상호작용에 대한 프레젠테이션 지연이 얼마나 지연되었는지 확인할 수 있습니다.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
});
이 데이터를 기록했을 때 웹사이트의 INP에 영향을 주는 상호작용의 표시 지연 시간이 길어진다면 원인은 다양할 수 있지만 주의해야 할 몇 가지 원인은 다음과 같습니다.
비용이 많이 드는 스타일 및 레이아웃 작업
긴 프레젠테이션 지연은 복잡한 CSS 선택자와 큰 DOM 크기 등 여러 가지 원인으로 인해 발생하는 스타일 재계산 및 레이아웃 작업 부담이 될 수 있습니다. web-vitals 라이브러리에 표시된 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.sort((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의 다른 데이터를 사용하여 프레임의 종료 시간을 결정하고 거기에서 스타일 및 레이아웃 작업의 시작 타임스탬프를 빼서 작업의 정확한 지속 시간을 계산할 수 있습니다.
<ph type="x-smartling-placeholder">장기 실행 requestAnimationFrame
콜백
긴 프레젠테이션 지연의 잠재적인 원인 중 하나는 requestAnimationFrame
콜백에서 실행되는 과도한 작업이기 때문입니다. 이 콜백의 콘텐츠는 이벤트 핸들러가 실행을 완료한 후, 스타일 재계산 및 레이아웃 작업 직전에 실행됩니다.
이러한 콜백 내에서 수행되는 작업이 복잡하면 이러한 콜백을 완료하는 데 상당한 시간이 걸릴 수 있습니다. requestAnimationFrame
로 실행하는 작업으로 인해 프레젠테이션 지연 값이 높다고 의심되는 경우 web-vitals 라이브러리에 표시되는 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.sort((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에 영향을 주거나 스타일을 업데이트하지 않는 다른 모든 작업은 다음 프레임이 불필요하게 지연되므로 주의하십시오.
결론
필드 데이터는 현장의 실제 사용자에게 어떤 상호작용이 문제가 되는지 파악할 때 활용할 수 있는 최고의 정보 소스입니다. web-vitals JavaScript 라이브러리 (또는 RUM 제공업체)와 같은 필드 데이터 수집 도구를 사용하면 어떤 상호작용이 가장 문제가 되는지 더 확신할 수 있고 실습에서 문제가 있는 상호작용을 재현한 후 수정할 수 있습니다.