前端性能监控怎么搞?我在裸辞半年后重新上岗的血泪实践

Rust练习生
2025-12-26 13:48
阅读 544

上个月刚结束半年的Gap期,重新回到上海的工位。说实话,这半年我睡得挺香,但一想到又要面对产品经理凌晨三点发来的“这个交互能不能再丝滑一点”的钉钉消息,心里还是有点发怵。不过嘛,生活总得继续,房贷也等不了——于是我火速入职了一家做SaaS工具的创业公司,坐标徐汇,离前司步行10分钟,房租没涨,算是一点小确幸。

入职第一天,CTO就扔给我一个任务:“我们前端性能太拉了,用户反馈页面卡成PPT,你搞个监控系统,下周上线。”
我当时内心OS:“大哥,我刚回来你就让我造火箭?” 但转念一想,这不正是我Gap期间刷GitHub、啃K8s、研究云原生时埋下的伏笔吗?行,干就完了。


为什么前端性能监控不是“可有可可无”?

先说背景。我们产品是个面向中小企业的低代码平台,前端用Vue3 + Vite,后端是Spring Boot微服务。用户在拖拽组件、配置流程时,如果页面响应慢超过2秒,流失率飙升30%——这是PM拿数据砸我脸时说的(感谢他,至少没甩锅)。

更惨的是,去年双11期间,因为某个第三方SDK加载超时,导致主应用白屏,客户投诉电话打爆了客服热线。运维兄弟半夜爬起来查日志,结果发现根本没打前端错误日志——前端异常就像幽灵,看不见摸不着,但用户能感受到

于是,我意识到:没有监控的前端,就像开着没仪表盘的车,你以为在高速前进,其实可能已经爆胎了


选型大战:自研 or 开源 or 商业?

我花了两天时间,在GitHub上狂翻项目,又对比了几家商业方案(比如Sentry、DataDog),最后列了个对比表:

方案 成本 上手难度 定制性 数据隐私 是否支持SPA
Sentry $$$ 依赖厂商
自研(基于Beacon + Spring Boot) $ 完全可控
LogRocket $$ 第三方存储
Perfume.js + 自建后端 $ 可控

考虑到公司预算紧张(创业公司懂的都懂),又不想把用户行为数据交给第三方,我决定走“轻量自研+开源轮子”路线

核心思路:

  • PerformanceObserverNavigation Timing API 采集关键指标(FCP、LCP、FID)
  • ErrorEvent 捕获 JS 异常
  • 通过 navigator.sendBeacon 发送日志(避免页面卸载时丢数据)
  • 后端用 Spring Boot 接收并存入 ClickHouse(之前团队搭好的)

关键代码:别怕,真不难

1. 前端采集器(精简版)

// performance-monitor.js
class PerformanceMonitor {
  constructor() {
    this.init();
  }

  init() {
    // 捕获JS错误
    window.addEventListener('error', (e) => {
      this.report({
        type: 'js_error',
        message: e.message,
        stack: e.error?.stack || '',
        url: window.location.href,
        timestamp: Date.now()
      });
    });

    // 捕获Promise rejection(很多人漏掉这个!)
    window.addEventListener('unhandledrejection', (e) => {
      this.report({
        type: 'promise_rejection',
        reason: e.reason?.toString() || 'Unknown',
        timestamp: Date.now()
      });
    });

    // 采集页面性能指标
    if ('performance' in window) {
      const perf = performance.getEntriesByType('navigation')[0];
      if (perf) {
        this.report({
          type: 'navigation_timing',
          fcp: this.getMetric('first-contentful-paint'),
          lcp: this.getMetric('largest-contentful-paint'),
          fid: this.getMetric('first-input-delay'), // 注意:FID需用Event Timing API
          dns: perf.domainLookupEnd - perf.domainLookupStart,
          tcp: perf.connectEnd - perf.connectStart,
          ttfb: perf.responseStart - perf.requestStart,
          load: perf.loadEventEnd - perf.loadEventStart
        });
      }
    }
  }

  getMetric(name) {
    // 使用PerformanceObserver监听特定指标
    return new Promise(resolve => {
      const po = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          if (entry.name === name) {
            resolve(entry.startTime);
            po.disconnect();
          }
        }
      });
      po.observe({ entryTypes: ['paint', 'largest-contentful-paint', 'first-input'] });
    });
  }

  report(data) {
    // 使用sendBeoncon确保页面关闭前也能发送
    const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
    navigator.sendBeacon('/api/perf/report', blob);
  }
}

// 启动监控
new PerformanceMonitor();

吐槽一句:FID(首次输入延迟)现在已经被INP(Interaction to Next Paint)取代了,但很多老项目还在用FID。如果你用的是Chrome 116+,建议直接上INP,不过得用 web-vitals 库。

2. 后端接收(Spring Boot)

// PerfReportController.java
@RestController
public class PerfReportController {

    @Autowired
    private PerfService perfService;

    @PostMapping("/api/perf/report")
    public ResponseEntity<?> report(@RequestBody String payload) {
        try {
            JSONObject data = JSON.parseObject(payload);
            // 简单校验
            if (data.getString("type") == null) {
                return ResponseEntity.badRequest().build();
            }
            perfService.save(data);
            return ResponseEntity.ok().build();
        } catch (Exception e) {
            log.error("Failed to parse perf report", e);
            return ResponseEntity.status(500).build();
        }
    }
}

这里有个坑:前端发的是Blob,Spring Boot默认用Jackson解析会失败。所以要么前端改发FormData,要么后端用@RequestBody String先接原始字符串再手动解析——我选了后者,省事。


工具链:让监控真正“活”起来

光有数据不够,得让人看得懂。我做了三件事:

  1. Grafana看板:把ClickHouse里的性能数据可视化,按页面、地域、设备类型分组。早上8点开工第一件事就是看昨天的LCP有没有超标。
  2. 企业微信告警:当某页面LCP > 4s 的比例超过5%,自动@我。上周五晚上11点就被@了,差点以为又要通宵——结果发现是测试环境域名配错了,虚惊一场。
  3. 本地开发插件:写了个Vite插件,在dev模式下实时显示当前页面的FCP/LCP,方便调优。再也不用开Chrome DevTools看Performance Tab了。

效果如何?数字不会骗人

上线一个月后,数据说话:

指标 优化前 优化后 降幅
平均LCP 3.8s 1.9s 50%
JS错误率 2.1% 0.4% 76%
页面完全加载时间 5.2s 2.6s 50%

最爽的是,PM终于不再说“感觉卡”了,而是拿着Grafana截图问:“为什么A页面比B页面慢0.3秒?” —— 从玄学到科学,这就是监控的价值


写在最后:代码人生,不止于写代码

这半年Gap,我一度怀疑自己是不是被技术淘汰了。但真正回归战场才发现,技术永远在变,但解决问题的思路是相通的。无论是K8s调度、Spring Boot优化,还是前端性能监控,核心都是:观测 → 分析 → 改进 → 验证

另外,别迷信大厂方案。我在前司用的是整套DataDog + RUM,功能强大但贵到肉疼。现在这套自研方案,虽然简陋,但够用、可控、成本低——适合自己的,才是最好的

如果你也在折腾前端监控,不妨去GitHub搜一下 web-vitalsperfume.js 这些开源库,站在巨人的肩膀上,少踩点坑。我的部分代码也打算脱敏后开源,等我忙完这波需求就传上去。

对了,今天又是8点起床干活的一天。窗外上海的雨下个不停,但看到LCP曲线一路下降,心情倒是晴朗得很。

毕竟,能让用户少等一秒,我们的代码人生,就多一分价值

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝