📌 前言
页面崩溃是影响用户体验的最严重事故 ------页面完全失效,代码停止执行,即使等待也无法自行恢复,甚至常规的错误监控也会随之失效。 
崩溃 vs 卡顿:卡顿是"暂时响应慢",崩溃是"彻底无法用"。崩溃发生后,页面上报能力归零,必须依赖浏览器机制。
🎯 方案选型对比
| 方案 | 实现原理 | ✅ 优势 | ❌ 劣势 | 可靠性 |
|---|---|---|---|---|
| 本地状态存储 | 崩溃前标记状态,二次进入上报 | 实现简单 | 依赖用户二次进入,4数据滞后,wu bao | ⭐⭐ |
| Service Worker 心跳 | SW 独立生命周期,跨页面检测 | 独立于页面,存活时间长 ;兼容性较reporting api高一点![]() |
误报率高(卡顿误判为崩溃) ;开发量大 | ⭐⭐⭐ |
| Reporting API 🏆 | 浏览器原生崩溃报告机制 | 页面崩溃后仍可上报,无需业务代码干预 | 兼容性要求高![]() |
⭐⭐⭐⭐⭐ |
核心结论 :Reporting API 是当前唯一能在页面彻底崩溃后仍可靠上报的技术方案。
reporting api 详解
1. 工作原理
浏览器通过 Reporting-Endpoints 响应头 声明崩溃报告接收地址。当页面发生崩溃时,浏览器在后台独立线程完成上报,页面主线程的终止不影响报告发送。
ReportingObserver(developer.chrome.com/blog/report... 适合开发者在前端主动监听和处理报告 (只支持获取 deprecation || intervention2种报告)
Reporting-Endpoints 如果希望浏览器自动上报崩溃等异常,需要通过 Reporting-Endpoints 响应头声明上报端点。 这种方式下,前端业务代码无需改动,只需配置响应头即可。
2. Reporting-Endpoints 上报字段分析
| 字段 | 说明 |
|---|---|
age |
报告的时间戳与当前时间之间的毫秒数。 |
body |
实际报告数据 ,已序列化为 JSON 字符串。报告的 body 中包含的字段由报告的 type 决定。 ⚠️ 注意 :不同类型的报告,其 body 结构不同。下面为 type = crash 时 body 包含的字段详解: 1.body.crash_report_api 可存放额外信息,读取自 window.crashReport 中的值。 2.body.reason 崩溃原因,取值包括: - oom:页面内存溢出 - unresponsive:页面长时间无响应,被浏览器强制终止 3.body.stack 崩溃时的 JavaScript 调用堆栈。body.reason 为 unresponsive且响应头需包含:document-policy = include-js-call-stacks-in-crash-reports(Chrome 137+ 开始支持) 该字段可从崩溃的文档中恢复调用堆栈。 3.body.is_top_level 崩溃页面是否属于顶级可遍历对象(top-level traversable)。 4.body.visibility_state 崩溃时页面的可见性状态,取值 visible 或 hidden。 |
type |
报告类型,例如 csp-violation、coep 等。 🎯 需要关注的是 :crash 类型。 |
url |
崩溃发生的页面地址。 |
user_agent |
生成报告时所使用的请求的 User-Agent 标头。 |
window.crashReport
window.crashReport的使用方法(可以携带一些额外的信息,便于后续分析),但需要注意的是,该api还在实验阶段,需要浏览器开启配置项后,才可以使用
dart
await window.crashReport.initialize('crash-report'); // 初始化
// 设置基础信息
window.crashReport.set('pageUrl', window.location.href) // set信息
window.crashReport.set('pageTitle', document.title)
//移除信息
window.crashReport.remove('pageTitle', document.title)
实现示例
在服务端配置Reporting-Endpoints头,default中是接受报告的服务器地址。由于window.crashReport还是实验api,那么如果一定需要传递额外信息,则可以给url上增加query来实现
less
app.use('/', express.static(path.join(__dirname, './public'), {
setHeaders: (res) => {
res.setHeader('Reporting-Endpoints', 'default="https://crash-report.free.beeceptor.com"');
}
}));
结果展示
内存暴涨导致的crash
json
[
{
"age": 10,
"body": {
"crash_report_api": {
"cookieEnabled": "true",
"crashTime": "2025-12-30T09:05:53.703Z",
"crashType": "oom",
"language": "en",
"memory": "[object Object]",
"pageLoadTime": "2025-12-30T09:05:48.822Z",
"pageTitle": "页面崩溃模拟 Demo",
"pageUrl": "<https://localhost:5000/crash.html>",
"platform": "MacIntel",
"referrer": "direct",
"screenColorDepth": "24",
"screenHeight": "1080",
"screenWidth": "1920",
"sessionId": "1767085548824-v4mushd65ib",
"timestamp": "1767085548823",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
"viewportHeight": "958",
"viewportWidth": "601"
},
"is_top_level": true,
"visibility_state": "visible"
},
"type": "crash",
"url": "https://localhost:5000/crash.html",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
}
]
报告接收服务搭建
- 支持跨域
- 支持解析
application/reports+json类型
javascript
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use((req, res, next) => {
// 设置 CORS 响应头
res.header('Access-Control-Allow-Origin', '*'); // 生产环境建议指定具体域名
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Max-Age', '86400'); // 预检请求缓存 24 小时
// 处理预检请求(OPTIONS)
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
// // 中间件:解析 JSON 请求体
app.use(bodyParser.json({type: 'application/json'}));
app.use(bodyParser.json({type: 'application/reports+json'}));
参考资料
1.window.crashReport:github.com/WICG/crash-...
2.zhuanlan.zhihu.com/p/40273861 如何监控网页崩溃

