要让 navigator.sendBeacon 在页面卸载时真正可靠地上报交互数据,关键不是“在卸载时才开始准备”,而是把数据生成、序列化、暂存全部前置——卸载那一刻只做转发。否则,哪怕 API 本身可靠,也会因同步卡顿、类型错误或时机错配而静默失败。

要让 navigator.sendBeacon 在页面卸载时真正可靠地上报交互数据,关键不是“在卸载时才开始准备”,而是把数据生成、序列化、暂存全部前置——卸载那一刻只做转发。否则,哪怕 API 本身可靠,也会因同步卡顿、类型错误或时机错配而静默失败。
提前收集并结构化埋点数据
所有用户交互(点击、曝光、路由变化、表单提交、接口完成)触发时,就立刻构造结构化对象并写入内存队列。不要等页面要关了再遍历 DOM 或读 localStorage。
- 每次埋点后调用
queue.push({ type: 'click', id: 'btn-submit', ts: Date.now() }) - 同步写入
localStorage做兜底(加时间戳 + UUID 防重复),例如localStorage.setItem('beacon_queue_v2', JSON.stringify(queue)) - 避免在
beforeunload中执行JSON.stringify(largeObj)或getComputedStyle()等耗时操作
用合规类型构造 payload,别传字符串
sendBeacon 对数据类型极其敏感:传 JSON.stringify(data) 这种字符串会静默失败(无报错、服务端收不到)。必须用浏览器认可的四类之一。
- 推荐用
new Blob([JSON.stringify(data)], { type: 'application/json' }) - 若需自定义 Content-Type(如
application/x-protobuf),只能靠 Blob 封装;直接传字符串会被强制设为text/plain - FormData 和 URLSearchParams 适合键值对简单上报,但不支持嵌套结构
绑定到更可靠的卸载生命周期钩子
beforeunload 在 iOS Safari、安卓 WebView 及 PWA 桌面版中常不触发;unload 更是最后时刻,几乎没执行窗口。应分层监听:
- 首选
pagehide:PWA、后台标签页、系统休眠前都会触发,且有足够时间发 beacon - 次选
visibilitychange(当document.visibilityState === 'hidden'):用户切走标签页时就 flush,覆盖“未关页但已离开”的场景 -
unload仅作日志记录或 fallback,不依赖它成功发送 - 所有事件监听都加
{ once: true },防止重复注册导致多次发送
主动清空队列,别全押在卸载那一刻
等待卸载才发,风险集中。实际应结合业务节奏主动 flush:
- 队列长度 ≥ 20 条时,立即调用
sendBeacon(url, blob)并清空已发项 - 路由跳转前(如 React 的
useEffect cleanup或 Vue 的beforeRouteLeave)尝试 flush - Chrome 单次 payload 上限约 64KB,超限直接拒绝(无提示),建议单条数据控制在 1–2KB 内
- 发送后从内存和 localStorage 中移除已成功项,避免重复上报










