대부분의 분석, 원격 분석, Core Web Vitals 분석 데이터는 사용자에게 응답하는 것보다 우선순위가 낮으므로 사용자가 페이지를 나갈 때까지 전송을 안전하게 지연할 수 있습니다. 단, 실시간 업데이트를 제공해야 하는 경우에는 적시성이 중요합니다.
비콘을 최소화하면서 실시간 분석 업데이트를 제공하는 최적의 방법은 activateAfter 구성 옵션과 함께 fetchLater()를 사용하는 것입니다. 이렇게 하면 특정 시간 내에 발생하는 모든 애널리틱스 이벤트를 단일 비콘으로 효과적으로 디바운스하고 일괄 처리할 수 있으며, 제한 시간이 만료되기 전에 사용자가 페이지를 떠나더라도 안정적으로 전송됩니다.
구현 방법
요청 예약: 관련 분석 데이터를 사용할 수 있게 되면 즉시 데이터 페이로드로
fetchLater()를 호출하고activateAfter값을 전달합니다. 이렇게 하면 해당 시간이 경과한 후 또는 사용자가 페이지를 미리 나간 경우 전송할 데이터를 큐에 추가합니다.여러 이벤트를 함께 일괄 처리:
activateAfter제한 시간이 만료되기 전에 새 분석 이벤트가 발생하면 이전에 예약된 요청을 취소하고 단일 페이로드에 전체 이벤트 대기열을 포함하여fetchLater()를 다시 호출합니다 (동일한activateAfter값 사용).제한 시간이 만료되면 이벤트 대기열 재설정: 예약된 비콘이 성공적으로 전송된 후 (즉,
fetchLater()결과의activated값이true인 경우) 새 애널리틱스 이벤트가 발생하면 이벤트 대기열을 재설정합니다.나머지는 브라우저에서 처리하도록 허용: 사용자가
activateAfter제한 시간이 만료되기 전에 다른 페이지로 이동하거나 탭을 닫아도 브라우저는 가장 최근의fetchLater()호출에서 페이로드를 안정적으로 전송합니다.
예시 코드
이 코드는 페이지의 모든 load 및 click 이벤트를 추적하고 10초 제한 시간 내에 발생하는 모든 이벤트를 일괄 처리합니다.
// Replace with your analytics endpoint.
const ANALYTICS_ENDPOINT = '/path/to/analytics/endpoint';
// Replace with a time window of your choice. All analytics events that
// occur within this time window will be batched together.
const BATCH_WINDOW = 10 * 1000;
// The maximum number of events to batch. Pick a number that is unlikely
// to overflow the fetchLater() quota for the page.
const MAX_QUEUE_SIZE = 100;
const eventQueue = [];
let fetchLaterResult;
let fetchLaterController;
function trackEvent(eventData) {
// If the previously queued beacon has already been sent, or if the
// max queue size has been met, reset the queue.
if (fetchLaterResult?.activated || eventQueue.length > MAX_QUEUE_SIZE) {
fetchLaterController = null;
fetchLaterResult = null;
eventQueue.length = 0;
}
eventQueue.push(eventData);
// Abort any pending beacons before creating a new one.
if (fetchLaterController) {
fetchLaterController.abort();
}
fetchLaterController = new AbortController();
// Schedule a fetch for the events to be sent when the batch window expires.
// IMPORTANT: wrap the call in a try/catch to handle quota errors.
try {
fetchLaterResult = fetchLater(ANALYTICS_ENDPOINT, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify(eventQueue),
signal: fetchLaterController.signal,
activateAfter: BATCH_WINDOW,
});
} catch (error) {
// Handle errors as needed.
}
}
// Track page loads.
window.addEventListener('load', () => {
trackEvent({type: 'page_load'});
});
// Track click events.
window.addEventListener('click', (event) => {
trackEvent({type: 'click', target: serializeElement(event.target)});
});
권장사항
activateAfter옵션이 설정된fetchLater()를 사용하여 서로 가까운 시점에 발생하는 여러 분석 이벤트를 일괄 처리합니다(권장).- 새로운 분석 이벤트가 발생하는 경우
AbortController를 사용하여 대기 중인 가져오기를 취소해야 합니다. - 권장사항: 너무 많은 이벤트를 일괄 처리하여
fetchLater()할당량 (현재 출처당 약 64KB)을 초과하지 않는지 확인합니다. fetchLater()호출을try/catch로 래핑하여 할당량 오류를 처리해야 합니다.globalThis에서fetchLater()의 존재를 감지하는 기능을 사용하고 API를 지원하지 않는 브라우저를 위한 대체 전략을 구현합니다.- 요청 본문에
ReadableStream객체를 사용하지 마세요. 오류가 발생합니다.
브라우저 지원 및 대체 전략
따라서 일반적으로 대체 전략이 필요합니다.
하지만 이 API의 향상된 안정성과 성능 이점을 고려할 때 브라우저에서 지원하는 경우 fetchLater()를 사용해야 합니다.
fetchLater() 폴리필
지원되지 않는 브라우저에서 API를 최대한 가깝게 구현하는 다음 최소 fetchLater() 폴리필을 사용합니다.
이 폴리필의 유일한 주목할 만한 동작 차이는 사용자가 페이지를 떠날 때 페이로드를 전송하는 대신 페이지의 visibilityState가 '숨김'으로 변경될 때마다 페이로드를 전송한다는 점입니다. 이는 현재 널리 사용되는 가장 안정적인 세션 종료 신호이기 때문입니다.
globalThis.fetchLater ??= function fetchLater(url, init = {}) {
let timeoutHandle;
let activated = false;
function sendNow() {
if (!(init.signal && init.signal.aborted)) {
// Use fetch keepalive if the browser supports it or if custom fetch
// parameters are specified (e.g. custom headers or methods).
// Otherwise fall back to `navigator.sendBeacon()`.
if (
'keepalive' in Request.prototype ||
init.method !== 'POST' ||
init.headers
) {
fetch(url, Object.assign({}, init, {keepalive: true}));
activated = true;
} else {
activated = navigator.sendBeacon(url, init.body);
}
}
destroy();
}
function destroy() {
document.removeEventListener('visibilitychange', sendNow);
clearTimeout(timeoutHandle);
}
if (document.visibilityState === 'hidden') {
// If the beacon was created while the page is already hidden, send data
// ASAP but wait until the next microtask to allow all sync code to run.
queueMicrotask(sendNow);
} else {
document.addEventListener('visibilitychange', sendNow);
if (typeof init.activateAfter === 'number' && init.activateAfter >= 0) {
timeoutHandle = setTimeout(sendNow, init.activateAfter);
}
}
if (init.signal) {
init.signal.addEventListener('abort', destroy);
}
return {
get activated() {
return activated;
},
};
};