
现代浏览器限制了 beforeunload 和 unload 事件的可用性,禁止弹窗、阻止异步操作,且不保证代码执行完成;推荐结合 beforeunload 基础钩子与 navigator.sendBeacon() 发送轻量数据,React/Next.js 中需注意服务端渲染兼容性和事件清理。
现代浏览器限制了 `beforeunload` 和 `unload` 事件的可用性,禁止弹窗、阻止异步操作,且不保证代码执行完成;推荐结合 `beforeunload` 基础钩子与 `navigator.sendbeacon()` 发送轻量数据,react/next.js 中需注意服务端渲染兼容性和事件清理。
在 Web 开发中,「在用户离开页面前执行一段逻辑」是一个常见需求(例如保存草稿、上报停留时长、清理 WebSocket 连接等)。但需明确:现代浏览器(Chrome ≥ 80、Firefox、Edge、Safari)已大幅削弱 beforeunload 和 unload 的能力——你无法可靠地执行异步请求(如 fetch、axios),alert()/confirm() 等阻塞式调用被完全禁用,且 unload 事件本身在多数场景下不会触发或立即终止,不可依赖。
✅ 正确做法是:仅在 beforeunload 中返回字符串(触发浏览器原生提示),并使用 navigator.sendBeacon() 发送关键数据。该方法是唯一被广泛支持、无需等待响应、且在页面卸载过程中仍能异步发送小量数据的标准化方案。
// ✅ 推荐:安全、可靠、符合规范
const handleBeforeUnload = (e) => {
// 1. 触发浏览器原生提示(仅当有实际需要时才设置)
// 注意:现代浏览器仅显示统一提示语,不展示自定义文本
e.preventDefault();
e.returnValue = ''; // 必须设置(兼容旧版),但内容会被忽略
// 2. 立即发送分析/日志数据(无等待、不阻塞卸载)
const data = JSON.stringify({
event: 'page_exit',
timestamp: Date.now(),
path: window.location.pathname
});
// sendBeacon 自动使用 POST,支持跨域(需服务端配置 CORS)
navigator.sendBeacon('/api/log', data);
};
// 注册监听(注意:仅在客户端挂载时添加)
if (typeof window !== 'undefined') {
window.addEventListener('beforeunload', handleBeforeUnload);
// ⚠️ 清理:在 React 组件卸载或 Next.js 路由跳转前移除(避免内存泄漏)
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}? 重要注意事项:
-
sendBeacon()仅支持POST请求,且 payload 类型为ArrayBufferView、Blob、FormData或URLSearchParams;字符串需转为Blob或使用new Blob([data], { type: 'application/json' }); - 不要尝试在
beforeunload中调用fetch()、setTimeout()或await—— 它们几乎必然失败; -
mouseout事件(如答案中建议)不是可靠替代方案:它在用户鼠标移出窗口时触发(如切换标签页、拖拽窗口、甚至悬停系统托盘都会触发),且无法区分“离开”和“暂时失焦”,易造成误报; - 在 Next.js 中,务必在
useEffect(客户端)中注册,并返回 cleanup 函数;SSR 阶段window不存在,需加typeof window !== 'undefined'判断; - 若需更精细控制(如路由级离开确认),React Router v6+ 提供
useBlocker,Next.js App Router 可结合useRouter().events.on('routeChangeStart')拦截导航(但仅对内部路由有效,无法捕获直接关闭标签页)。
? 总结:放弃 alert() 和 unload,拥抱 beforeunload + sendBeacon 组合。它是当前唯一兼顾兼容性、可靠性与规范性的方案。对于用户体验敏感的操作(如未保存表单),应优先使用 beforeunload 返回非空字符串触发提示,再辅以后台静默上报,而非试图“拦截”用户行为。

















