页面慢你只会看 Lighthouse,今天我把真实用户的性能数据全接进了监控里——User Timing API 生产落地完整方案
写完代码上线了,Lighthouse 每次都绿,但用户反馈「页面还是慢」——这个问题我盯了三年,今天终于把答案找到了:Lighthouse 测的是实验室数据,真正反映用户真实体验的,是你自己的业务指标。
Chrome 给了一套浏览器内置的性能监控 API,User Timing API 完整跑下来可以把任意业务操作的耗时数据直接接进你的监控系统里。这套方案不需要任何第三方依赖,一行代码就能跑。
核心就三个方法:mark、measure、Observer
performance.mark('renderStart') 在关键节点打标记,performance.measure('renderDuration', 'renderStart', 'renderEnd') 计算两个标记之间的耗时,然后用 PerformanceObserver 实时监听这些数据的变化。
// 在关键操作前后打标记
performance.mark('fetchStart');
fetchData().then(() => performance.mark('fetchEnd'));
// 计算耗时
performance.measure('fetchDuration', 'fetchStart', 'fetchEnd');
// 监听 measure 结果,实时上报
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// 上报到你的监控系统
sendToAnalytics({ name: entry.name, duration: entry.duration });
});
});
observer.observe({ entryTypes: ['measure'] });
三个常见坑:mark 重名、measure 顺序、Observer 过期
第一个坑是 mark 重名。同一个名字打两次会直接抛 SyntaxError,生产环境里多个组件同时打标记很容易撞名。解法是用 Symbol 或者加唯一前缀:performance.mark(fetchStart-${componentId})。
第二个坑是 measure 的顺序。mark(‘end’) 在 mark(‘start’) 之前打,measure 会返回一个负数,生产排查的时候很容易误判。解法是加一个顺序校验,或者直接用 performance.measure('duration', { start: t1, end: t2 }) 的对象语法绕过顺序依赖。
第三个坑是 Observer 只监听之后的数据。observer.observe() 之后的 mark 才会被捕获,之前打的标记不会触发回调。解法是在页面加载初期就初始化 Observer,或者用 performance.getEntriesByType('measure') 主动拉取已有数据。
生产落地:我接进了这三个核心指标
第一个是首屏可用时间。不同于 LCP 只关注最大内容元素,我用 mark 记录「页面关键数据加载完成」和「首屏可交互」两个时间点,这个才是用户真正能用的时刻。
// 首屏可交互计时
performance.mark('interactive');
// 配合 PerformanceNavigationTiming 获取真实加载时间
const [navEntry] = performance.getEntriesByType('navigation');
const ttci = navEntry.domContentLoadedEventEnd; // 可交互时间
第二个是 API 响应时长分布。不只看平均响应时间,我按快(<200ms)、中(200-500ms)、慢(>500ms)三个档位统计分布,监控 P99 而不是平均值——用户感知的是最慢的那 1% 请求。
第三个是用户操作响应时长。给按钮点击、列表滚动、表单提交这些关键操作打标记,收集真实用户的操作响应数据,这个指标比任何跑分都真实。
这套方案能接进任何监控平台
数据格式是标准的 PerformanceEntry,duration 字段直接就是毫秒数,不需要做任何转换。接 Sentry 的话用 performance.measure() 的 name 做 event 标签,接自建平台直接 POST 到你的数据端点就行。
// 完整上报示例
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify({
type: entry.entryType,
name: entry.name,
duration: Math.round(entry.duration),
timestamp: Date.now(),
url: location.pathname
})
});
});
});
observer.observe({ entryTypes: ['measure', 'mark'] });
看完这篇文章,下一步很简单:在你的关键业务组件里找两个打点位置,一个开始、一个结束,把数据接进你现有的监控系统——这大概只需要 20 行代码,但你拿到的,是 Lighthouse 永远给不了的东西:真实用户的性能数据。
评论区
登录后可评论。