Beacon APIを使用したバックグラウンドデータ送信

ページナビゲーションは、アナリティクスリクエストによって人質にされるべきではありません。ユーザーがリンクをクリックしたり、タブを閉じたりする際、従来のHTTPリクエストは完全にブロックされたり失敗したりする可能性があり、不完全なデータと不満を抱えたユーザーを残すことになります。Beacon APIソリューションは、リクエストをバックグラウンドでキューに入れることでこれを変え、パフォーマンスに影響を与えることなくデータがサーバーに確実に到達するようにします。
この記事では、信頼性の高いバックグラウンドデータ送信のためにnavigator.sendBeacon()
を実装する方法を説明します。アナリティクストラッキングやエラーレポートなどの実用的なアプリケーションを学び、ページ遷移時にfetch()
よりも優れている理由を理解し、モダンJavaScriptを使用した完全な実装を確認できます。
重要なポイント
- Beacon APIは、ページナビゲーションをブロックすることなく、バックグラウンドでリクエストをキューに入れます
- アナリティクス、エラーレポート、ユーザーアクショントラッキングには
navigator.sendBeacon()
を使用してください - ビーコンは、ページアンロードイベント時に
fetch()
よりも信頼性が高いです - 最適なパフォーマンスのために、ペイロードを小さく保ち、複数のイベントをバッチ処理してください
- Beacon APIをサポートしていない古いブラウザ向けのフォールバックを実装してください
- APIは配信確認ではなく、キューイングが成功したかどうかを示すブール値を返します
Beacon APIの違いとは
Beacon APIアプローチは根本的な問題を解決します:従来のHTTPリクエストはページナビゲーションをブロックするということです。アンロードイベント時にfetch()
やXMLHttpRequest
を使用すると、ブラウザはナビゲーションを進める前にリクエストの完了を待つ必要があります。これにより、ユーザーエクスペリエンスが悪化し、データ送信が不安定になります。
Beacon APIは異なる動作をします。リクエストを非同期でキューに入れ、ページが閉じられた後でもバックグラウンドで送信を処理します。ブラウザは、ナビゲーションをブロックしたり、JavaScriptをアクティブに保つ必要なく、プロセス全体を管理します。
ページ遷移時に従来の方法が失敗する理由
fetch()
を使用した一般的なシナリオを考えてみましょう:
window.addEventListener('beforeunload', () => {
// これは失敗したり、ナビゲーションをブロックしたりする可能性があります
fetch('/analytics', {
method: 'POST',
body: JSON.stringify({ event: 'page_exit' })
})
})
このアプローチにはいくつかの問題があります:
- ページがアンロードされるときにリクエストがキャンセルされる可能性があります
- リクエストが完了するまでナビゲーションがブロックされます
- データがサーバーに到達する保証がありません
- ページ遷移の遅延により、ユーザーエクスペリエンスが悪化します
navigator.sendBeacon()の動作原理
navigator.sendBeacon()
メソッドは、URLエンドポイントとオプションのデータという2つのパラメータを受け取ります。リクエストが正常にキューに入れられたかどうか(サーバーに到達したかどうかではない)を示すブール値を返します。
const success = navigator.sendBeacon(url, data)
ブラウザは実際の送信を処理し、ネットワーク条件とシステムリソースに最適化します。このfire-and-forgetアプローチは、レスポンスが不要なアナリティクス、ログ記録、診断データに最適です。
実用的な実装例
基本的なアナリティクストラッキング
ユーザーセッションを追跡するための最小限の実装は次のとおりです:
// セッションデータを追跡
const sessionData = {
userId: 'user123',
sessionDuration: Date.now() - sessionStart,
pageViews: pageViewCount,
timestamp: new Date().toISOString()
}
// ページが非表示になったり、ユーザーが移動したりするときにデータを送信
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const payload = JSON.stringify(sessionData)
navigator.sendBeacon('/log', payload)
}
})
window.addEventListener('beforeunload', () => {
const payload = JSON.stringify(sessionData)
navigator.sendBeacon('/log', payload)
})
エラーレポートシステム
Beacon APIはJavaScriptエラーのキャプチャとレポートに優れています:
window.addEventListener('error', (event) => {
const errorData = {
message: event.message,
filename: event.filename,
line: event.lineno,
column: event.colno,
stack: event.error?.stack,
userAgent: navigator.userAgent,
timestamp: Date.now()
}
if (navigator.sendBeacon) {
navigator.sendBeacon('/log', JSON.stringify(errorData))
}
})
ユーザーアクショントラッキング
インターフェースをブロックすることなく、特定のユーザーインタラクションを追跡します:
function trackUserAction(action, details) {
const actionData = {
action,
details,
timestamp: Date.now(),
url: window.location.href
}
if (navigator.sendBeacon) {
navigator.sendBeacon('/log', JSON.stringify(actionData))
}
}
// 使用例
document.getElementById('cta-button').addEventListener('click', () => {
trackUserAction('cta_click', { buttonId: 'cta-button' })
})
document.addEventListener('scroll', throttle(() => {
const scrollPercent = Math.round(
(window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100
)
trackUserAction('scroll', { percentage: scrollPercent })
}, 1000))
完全なアナリティクス実装
複数のトラッキングシナリオを組み合わせた包括的な例を以下に示します:
class AnalyticsTracker {
constructor(endpoint = '/log') {
this.endpoint = endpoint
this.sessionStart = Date.now()
this.events = []
this.setupEventListeners()
}
setupEventListeners() {
// ページの可視性変更を追跡
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.sendBatch()
}
})
// ページアンロードを追跡
window.addEventListener('beforeunload', () => {
this.sendBatch()
})
// エラーを追跡
window.addEventListener('error', (event) => {
this.trackEvent('error', {
message: event.message,
filename: event.filename,
line: event.lineno
})
})
}
trackEvent(type, data) {
this.events.push({
type,
data,
timestamp: Date.now()
})
// イベントが多すぎる場合はバッチを送信
if (this.events.length >= 10) {
this.sendBatch()
}
}
sendBatch() {
if (this.events.length === 0) return
const payload = {
sessionId: this.generateSessionId(),
sessionDuration: Date.now() - this.sessionStart,
events: this.events,
url: window.location.href,
userAgent: navigator.userAgent
}
if (navigator.sendBeacon) {
const success = navigator.sendBeacon(
this.endpoint,
JSON.stringify(payload)
)
if (success) {
this.events = [] // 送信されたイベントをクリア
}
}
}
generateSessionId() {
return Math.random().toString(36).substring(2, 15)
}
}
// トラッカーを初期化
const tracker = new AnalyticsTracker('/log')
// カスタムイベントを追跡
tracker.trackEvent('page_view', { page: window.location.pathname })
ブラウザサポートとフォールバック
Beacon APIは、モダンブラウザ全体で優れたブラウザサポートを持っています。古いブラウザの場合は、適切なフォールバックを実装してください:
function sendData(url, data) {
if (navigator.sendBeacon) {
return navigator.sendBeacon(url, data)
}
// 古いブラウザ向けのフォールバック
try {
const xhr = new XMLHttpRequest()
xhr.open('POST', url, false) // アンロードイベント用の同期処理
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.send(data)
return true
} catch (error) {
console.warn('Failed to send data:', error)
return false
}
}
Beacon実装のベストプラクティス
ペイロードを小さく保つ
Beacon APIは小さなデータパケット用に設計されています。ペイロードを必要な情報に制限してください:
// 良い例:焦点を絞った必要なデータ
const essentialData = {
event: 'conversion',
value: 29.99,
timestamp: Date.now()
}
// 避けるべき例:大きな不要なデータ
const bloatedData = {
event: 'conversion',
value: 29.99,
timestamp: Date.now(),
entireDOMState: document.documentElement.outerHTML,
allCookies: document.cookie,
completeUserHistory: getUserHistory()
}
複数のイベントをバッチ処理する
各イベントに対して個別のビーコンを送信する代わりに、それらをまとめてバッチ処理してください:
const eventBatch = []
function addEvent(eventData) {
eventBatch.push(eventData)
// バッチが一定のサイズに達したら送信
if (eventBatch.length >= 5) {
sendBatch()
}
}
function sendBatch() {
if (eventBatch.length > 0) {
navigator.sendBeacon('/log', JSON.stringify(eventBatch))
eventBatch.length = 0 // バッチをクリア
}
}
ネットワーク障害を適切に処理する
ビーコンはレスポンスフィードバックを提供しないため、クライアントサイドの検証を実装してください:
function validateAndSend(data) {
// データ構造を検証
if (!data || typeof data !== 'object') {
console.warn('Invalid data for beacon')
return false
}
// ペイロードサイズをチェック(ブラウザは通常64KBに制限)
const payload = JSON.stringify(data)
if (payload.length > 65536) {
console.warn('Payload too large for beacon')
return false
}
return navigator.sendBeacon('/log', payload)
}
ページ遷移時のBeacon API vs Fetch
主な違いは、ページアンロードイベント時に明らかになります:
// Beacon API:ノンブロッキング、信頼性が高い
window.addEventListener('beforeunload', () => {
navigator.sendBeacon('/log', JSON.stringify(data)) // ✅ 信頼性が高い
})
// Fetch API:ブロックしたり失敗したりする可能性がある
window.addEventListener('beforeunload', () => {
fetch('/log', {
method: 'POST',
body: JSON.stringify(data),
keepalive: true // 役立つが成功を保証しない
}) // ❌ ナビゲーション中に失敗する可能性がある
})
fetchリクエストのkeepalive
フラグは同様の機能を提供しますが、このユースケース専用に設計されたBeacon APIほどの信頼性はありません。
結論
Beacon APIは、ページナビゲーションをブロックすることなく、バックグラウンドデータ送信のための堅牢なソリューションを提供します。リクエストを非同期でキューに入れることで、最適なユーザーエクスペリエンスを維持しながら、信頼性の高いデータ配信を保証します。アナリティクストラッキング、エラーレポート、ユーザーアクションログの実装のいずれであっても、navigator.sendBeacon()
は、ページ遷移時に従来のHTTPメソッドでは実現できない信頼性とパフォーマンスを提供します。
ビーコンのfire-and-forget特性により、データを送信する必要があるがレスポンスが不要なシナリオに最適です。適切なバッチ処理戦略とペイロード最適化と組み合わせることで、Beacon APIは、データ収集とユーザーエクスペリエンスの両方を優先するモダンWebアプリケーションにとって不可欠なツールとなります。
よくある質問
Beacon APIは、レスポンスを待つことなく少量のデータをサーバーに送信するJavaScript Web標準です。fetchやXMLHttpRequestとは異なり、ビーコンリクエストはブラウザによってキューに入れられ、非同期で送信されるため、従来のリクエストが失敗したりユーザーエクスペリエンスをブロックしたりする可能性があるページナビゲーションイベント時のアナリティクスやログ記録に最適です。
いいえ、Beacon APIは小さなデータパケット用に設計されており、通常、ほとんどのブラウザで64KBに制限されています。より大量のデータを送信する必要がある場合は、より小さなリクエストをバッチ処理するか、重要でないタイミングシナリオではkeepaliveフラグ付きのFetch APIなどの代替方法を使用することを検討してください。
Beacon APIは、Chrome、Firefox、Safari、Edgeを含むすべてのモダンブラウザで優れたサポートを持っています。Internet Explorerでは動作しません。古いブラウザサポートのためには、XMLHttpRequestやfetchを使用したフォールバックを実装してください。ただし、これらの代替手段は、ページアンロードイベント時に同じ信頼性を提供しない可能性があります。
navigator.sendBeaconメソッドは、サーバーに到達したかどうかではなく、リクエストがブラウザによって正常にキューに入れられたかどうかを示すブール値を返します。ビーコンはfire-and-forgetであるため、サーバーレスポンスにアクセスしたり、データが受信されたかどうかを確実に知ることはできません。これは最適なパフォーマンスのための設計です。
ページ遷移、アンロードイベント、またはページが非表示になったときにデータを送信する必要があり、レスポンスが不要な場合は、Beacon APIを使用してください。レスポンス、エラーを処理する必要がある場合、またはタイミングがページナビゲーションにとって重要でない場合は、通常のAPIコールにfetchを使用してください。