Отправка фоновых данных с помощью Beacon API

Навигация по страницам не должна блокироваться запросами аналитики. Когда пользователи кликают по ссылке или закрывают вкладку, традиционные HTTP-запросы могут блокироваться или полностью завершаться неудачей, оставляя вас с неполными данными и разочарованными пользователями. Решение Beacon API изменяет это, ставя запросы в очередь в фоновом режиме, обеспечивая доставку ваших данных на сервер без влияния на производительность.
Эта статья демонстрирует, как реализовать navigator.sendBeacon()
для надежной передачи фоновых данных. Вы изучите практические применения, такие как отслеживание аналитики и отчеты об ошибках, поймете, почему он превосходит fetch()
во время переходов между страницами, и увидите полную реализацию с использованием современного JavaScript.
Ключевые выводы
- Beacon API ставит запросы в очередь в фоновом режиме без блокировки навигации по страницам
- Используйте
navigator.sendBeacon()
для аналитики, отчетов об ошибках и отслеживания действий пользователей - Beacons более надежны, чем
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-адрес конечной точки и необязательные данные. Он возвращает булево значение, указывающее, был ли запрос успешно поставлен в очередь (а не достиг ли он сервера).
const success = navigator.sendBeacon(url, data)
Браузер обрабатывает фактическую передачу, оптимизируя под условия сети и системные ресурсы. Этот подход “выстрелил и забыл” идеален для аналитики, логирования и диагностических данных, где вам не нужен ответ.
Примеры практической реализации
Базовое отслеживание аналитики
Вот минимальная реализация для отслеживания пользовательских сессий:
// Отслеживание данных сессии
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('Не удалось отправить данные:', 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()
}
Группируйте несколько событий
Вместо отправки отдельных beacons для каждого события, группируйте их вместе:
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 // Очистить пакет
}
}
Изящно обрабатывайте сетевые сбои
Поскольку beacons не предоставляют обратную связь об ответе, реализуйте клиентскую валидацию:
function validateAndSend(data) {
// Валидация структуры данных
if (!data || typeof data !== 'object') {
console.warn('Неверные данные для beacon')
return false
}
// Проверка размера полезной нагрузки (браузеры обычно ограничивают до 64KB)
const payload = JSON.stringify(data)
if (payload.length > 65536) {
console.warn('Полезная нагрузка слишком большая для beacon')
return false
}
return navigator.sendBeacon('/log', payload)
}
Beacon API против 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 // Помогает, но не гарантирует успех
}) // ❌ Может не сработать во время навигации
})
Флаг keepalive
в fetch-запросах предоставляет похожую функциональность, но с меньшей надежностью, чем Beacon API, который был специально разработан для этого случая использования.
Заключение
Beacon API предоставляет надежное решение для передачи фоновых данных без блокировки навигации по страницам. Ставя запросы в асинхронную очередь, он обеспечивает надежную доставку данных при сохранении оптимального пользовательского опыта. Независимо от того, реализуете ли вы отслеживание аналитики, отчеты об ошибках или логирование действий пользователей, navigator.sendBeacon()
предлагает надежность и производительность, которые традиционные HTTP-методы не могут обеспечить во время переходов между страницами.
Природа “выстрелил и забыл” у beacons делает их идеальными для сценариев, где вам нужно отправить данные, но не требуется ответ. В сочетании с правильными стратегиями группировки и оптимизацией полезной нагрузки, Beacon API становится незаменимым инструментом для современных веб-приложений, которые приоритизируют как сбор данных, так и пользовательский опыт.
Часто задаваемые вопросы
Beacon API — это веб-стандарт JavaScript, который отправляет небольшие объемы данных на сервер без ожидания ответа. В отличие от fetch или XMLHttpRequest, beacon-запросы ставятся в очередь браузером и отправляются асинхронно, что делает их идеальными для аналитики и логирования во время событий навигации по страницам, где традиционные запросы могут не сработать или заблокировать пользовательский опыт.
Нет, Beacon API предназначен для небольших пакетов данных, обычно ограниченных 64KB в большинстве браузеров. Если вам нужно отправить большие объемы данных, рассмотрите группировку меньших запросов или использование альтернативных методов, таких как Fetch API с флагом keepalive для некритичных по времени сценариев.
Beacon API имеет отличную поддержку во всех современных браузерах, включая Chrome, Firefox, Safari и Edge. Он не работает в Internet Explorer. Для поддержки старых браузеров реализуйте резервный вариант с использованием XMLHttpRequest или fetch, хотя эти альтернативы могут не обеспечить такую же надежность во время событий выгрузки страницы.
Метод navigator.sendBeacon возвращает булево значение, указывающее, был ли запрос успешно поставлен в очередь браузером, а не достиг ли он сервера. Поскольку beacons работают по принципу 'выстрелил и забыл', вы не можете получить доступ к ответу сервера или точно знать, были ли данные получены. Это сделано специально для оптимальной производительности.
Используйте Beacon API, когда вам нужно отправить данные во время переходов между страницами, событий выгрузки или когда страница становится скрытой, и вам не нужен ответ. Используйте fetch для обычных API-вызовов, где вам нужно обрабатывать ответы, ошибки или когда время не критично для навигации по страницам.