前端卡顿监控的最后一块拼图:我把 STW Sentinel 接进真实业务,终于分清了 React 卡顿和 V8 GC

0. 开场:页面卡了,老板只问一句话

用户说页面卡。产品说转化掉了。后端说接口很快。前端打开 DevTools,只看到一坨 Long Task。

于是所有人开始猜:是不是 React 组件太多?是不是列表没虚拟滚动?是不是 CSS layout thrashing?是不是 Chrome 又抽风?

传统前端监控只能告诉你"卡了",但很难告诉你"是谁让世界暂停了"。

这里顺手点名 rAFPerformanceObserver、Long Task、web-vitals 的局限:它们都在主线程语境里观察主线程。

1. 为什么传统卡顿监控会失明?

核心论点:如果监控代码和业务代码在同一个线程,它们会一起死。

1.1 requestAnimationFrame

能看到帧间隔变大,但它自己也被主线程调度影响。它像是在心脏停跳后醒来补记一笔:"刚才好像断片了 700ms。"

1.2 Long Task API

能看到超过 50ms 的主线程长任务,但它更擅长记录 JS 执行和任务阻塞,不等于能精确切开 V8 STW 的瞬间。

1.3 DevTools Performance

适合开发环境复盘,但不适合生产环境持续采样。用户现场不会帮你开 DevTools。

这一节的结尾要引出:我们需要一个不坐在主线程里的观察者。

2. STW Sentinel 的定位:不是替代 web-vitals,而是补上黑匣子

这节非常关键。不要把 stw-sentinel 写成"吊打所有监控"。更高级的写法是:

web-vitals 看用户体验结果,Long Task 看主线程任务,STW Sentinel 看主线程之外的物理心跳。

监控手段能回答的问题盲区
web-vitals用户体验是否变差很难解释底层原因
Long Task主线程是否被长任务占用不一定能区分业务 JS、Layout、GC
rAF delta帧是否断了采样者自己也会被卡住
STW Sentinel主线程冻结期间外部时间是否仍稳定流逝需要 COOP/COEP 与 AudioWorklet 环境

STW Sentinel 不是性能监控的全部,而是卡顿归因链路里缺失的那颗钉子。

3. 生产接入架构:不要只 console.warn,要做事件归因

不要只记录 deltaMs,要记录上下文。

import { STWSentinel } from 'stw-sentinel'

const sentinel = new STWSentinel({
  thresholdMs: 10,
  onSpike: (deltaMs, entry) => {
    reportSTW({
      deltaMs,
      timestamp: performance.now(),
      route: location.pathname,
      visibility: document.visibilityState,
      userAgent: navigator.userAgent,
      recentAction: getLastUserAction(),
      recentLongTasks: getRecentLongTasks(),
      memory: getMemorySnapshotSafely(),
    })
  },
})

建议上报字段:

字段作用
deltaMsSTW 或调度尖峰长度
route哪个页面最容易卡
recentAction是否发生在点击、输入、滚动之后
recentLongTasks和 Long Task 做交叉验证
visibilityState排除后台标签页误判
deviceMemory低端设备分层
hardwareConcurrencyCPU 核心数分层
browserChrome / Edge / Safari 差异
releaseVersion对应前端版本回归

4. 卡顿归因矩阵:如何判断是谁的锅?

情况 A:Long Task 高,STW 不高

结论倾向:业务 JS、React render、同步计算、JSON parse、大循环、第三方 SDK。

处理方向:

情况 B:Long Task 高,STW 也高

结论倾向:业务代码制造了内存压力,触发 V8 GC/STW。

典型场景:

情况 C:STW 高,但 Long Task 不明显

结论倾向:传统主线程观测没抓到完整现场,或者 GC 停顿发生在监控盲区。

处理方向:

情况 D:rAF 掉帧,但 STW 稳定

结论倾向:渲染、布局、合成、GPU、CSS、图片解码等问题。

处理方向:

5. 一个真实案例:React 页面卡顿,最后不是 React 的锅

案例结构:

我们不是让 V8 不 GC,而是减少把 V8 逼到 Stop-The-World 的概率。

6. 阈值怎么设:不要迷信 16.6ms

推荐策略:

阈值不是物理真理,是业务容忍度。 游戏、音频、交易、编辑器、看板、后台管理系统的阈值不一样。

7. 生产环境注意事项:这把武器有保险

7.1 COOP/COEP 会影响资源加载

很多人配置 Cross-Origin-Embedder-Policy: require-corp 后,会发现第三方图片、脚本、iframe、CDN 资源出问题。

建议:

7.2 AudioContext 必须用户手势后启动

建议:

7.3 不要全量上报所有心跳

生产环境只上报异常尖峰和少量采样窗口。

7.4 兼容性要诚实

不是所有浏览器、所有嵌入环境都适合跑这套东西。尤其是微信内置浏览器、企业内嵌 WebView、老 Safari、跨域资源复杂的老项目,都要给降级策略。

8. 升维:前端性能监控要从"指标"走向"物理观测"

过去我们用指标描述用户体验:LCP、FID、INP、CLS。现在我们还需要一层更底层的东西:物理心跳。

因为当主线程停止呼吸时,所有跑在主线程里的监控都会变成事后回忆。

STW Sentinel 不是为了证明 AudioWorklet 有多酷,而是为了把前端卡顿从玄学、猜测和甩锅,拉回到可观测、可归因、可复现的工程系统里。

如果你只想试一下,5 行代码接入。

npm install stw-sentinel

如果你想定位真实业务卡顿,请记录上下文、交叉 Long Task、按路由和设备聚合。

页面卡了不可怕,可怕的是你不知道它为什么卡。

← 返回博客列表