Vite 项目的 Web Vitals 与性能监控:指标采集、构建期埋点与 RUM 上报

在 Vite 项目里落地真实用户性能监控:LCP、INP、CLS 三个核心指标的含义与采集方式、web-vitals 库与 PerformanceObserver 的用法、构建期注入上报脚本与版本标记、RUM 的采样与批量上报、指标归因到具体资源、Lighthouse CI 与性能预算、产物体积与指标的关联,以及重复上报与采样偏差等高频陷阱。

引言

性能优化最怕「凭感觉」:改了构建配置、拆了 chunk,到底有没有让真实用户变快?答案只能来自真实用户监控(RUM)——把 LCP、INP、CLS 这些 Core Web Vitals 指标从真实浏览器里采集回来,再和构建产物、版本号关联起来分析。

本文从三个核心指标讲起,逐步搭建采集(web-vitals 库)、构建期埋点(注入脚本与版本标记)、上报(采样与 beacon 传输)与归因(落到具体资源)的完整链路,再讨论 Lighthouse CI 的实验室数据、产物体积与指标的关联,最后给出持续监控与高频陷阱的排查清单。

前置:/vite-runtime-performance-optimization/(运行期性能优化)、/vite-build-optimization/(构建产物优化)。产物分析见 /vite-bundle-analysis-performance/。


目录


1. Core Web Vitals 的三个指标:LCP、INP 与 CLS

1.1 三个指标的含义

指标衡量良好阈值
LCP最大内容元素渲染时间≤ 2.5s
INP交互到下次绘制的延迟≤ 200ms
CLS累计布局偏移≤ 0.1

1.2 为什么是这三个

它们分别对应加载、交互、视觉稳定三个体验维度,且都能被浏览器原生观测。相比 FCP、TTI 等旧指标,它们更贴近用户真实感受,也更容易被 RUM 采集。

LCP 差 → 首屏资源太重、关键 CSS 未内联、图片未优化
INP 差 → 主线程被长任务占用、事件处理过重
CLS 差 → 图片无尺寸、字体加载抖动、动态插入内容

记忆:三个核心指标对应加载、交互、稳定——LCP 看首屏资源、INP 看主线程长任务、CLS 看尺寸与字体抖动。


2. 指标采集:web-vitals 库与 PerformanceObserver

2.1 用 web-vitals 采集

官方 web-vitals 库把底层 PerformanceObserver 的细节封装好了:

import { onLCP, onINP, onCLS } from 'web-vitals'

function report(metric) {
  console.log(metric.name, metric.value, metric.rating)
}

onLCP(report)
onINP(report)
onCLS(report)

2.2 底层是 PerformanceObserver

理解底层有助于排查采集不到的情况:

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // largest-contentful-paint / layout-shift / event
    console.log(entry.entryType)
  }
}).observe({ type: 'largest-contentful-paint', buffered: true })

2.3 关键时机

LCP   → 页面隐藏或首次交互后最终确定
INP   → 整段会话中持续更新,取最差交互
CLS   → 会话窗口内累计,页面隐藏时最终确定

因此指标必须在 visibilitychange(页面隐藏)时再最终上报一次,否则会漏掉最终值。

记忆:用 web-vitals 库采集、底层是 PerformanceObserver——指标在页面隐藏时才最终确定,务必在 visibilitychange 时补报一次。


3. 构建期埋点:注入上报脚本与版本标记

3.1 注入版本号

把构建版本注入代码,才能把指标与具体发布关联:

export default defineConfig({
  define: {
    __BUILD_VERSION__: JSON.stringify(process.env.GIT_SHA ?? 'dev'),
  },
})
// 上报时带上版本
sendBeacon('/rum', JSON.stringify({ name, value, version: __BUILD_VERSION__ }))

3.2 只进生产构建

监控脚本不应进开发态,避免污染数据:

if (import.meta.env.PROD) {
  const { initRUM } = await import('./rum')
  initRUM()
}

import.meta.env.PROD 在开发态为 false,配合动态导入可以让整个监控模块在 dev 里被 tree-shake 掉。

3.3 上报端点配置

const RUM_ENDPOINT = import.meta.env.VITE_RUM_ENDPOINT

用环境变量区分测试与生产的上报地址。

记忆:构建期把版本号注入(define)、监控脚本用 import.meta.env.PROD 包住——既能把指标和发布关联,又不会污染开发态数据。


4. RUM 上报:采样、批量与 beacon 传输

4.1 用 sendBeacon 传输

页面卸载时同步 XHR 会被中断,必须用 navigator.sendBeacon:

function send(payload: object) {
  const body = JSON.stringify(payload)
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/rum', body)
  } else {
    fetch('/rum', { method: 'POST', body, keepalive: true })
  }
}

4.2 采样

高流量站点必须采样,否则上报量爆炸:

const SAMPLE_RATE = 0.1
const sampled = Math.random() < SAMPLE_RATE
if (sampled) send(metric)

4.3 批量与合并

把一次会话里的多个指标合并成一条上报,减少请求数:

const queue: object[] = []
function enqueue(metric: object) {
  queue.push(metric)
  if (queue.length >= 5) flush()
}
function flush() {
  if (queue.length) send({ metrics: queue.splice(0) })
}

记忆:上报用 sendBeacon(卸载时不被中断)、高流量必须采样、多指标合并成一条批量发——三点做到才不至于把监控变成新的性能负担。


5. 归因分析:把指标落到具体资源与元素

5.1 采集归因信息

只有数值没有归因,优化就无从下手。web-vitals 提供了归因构建:

import { onLCP } from 'web-vitals/attribution'

onLCP((metric) => {
  console.log(metric.attribution)
  // element: 最大内容元素
  // url: 触发 LCP 的资源
  // timeToFirstByte / resourceLoadDelay ...
})

5.2 关键归因字段

指标关键归因
LCP元素选择器、资源 URL、TTFB 分解
INP目标元素、事件类型、脚本 URL
CLS偏移来源元素、偏移量

5.3 把归因与产物关联

归因里拿到的资源 URL 自带 hash,正好可以和构建产物对应:

归因 url: /assets/hero-a1b2c3.webp
→ 在产物清单里定位到具体文件与体积
→ 决定是压缩图片还是改懒加载

记忆:归因是优化的前提——用 web-vitals/attribution 拿到元素与资源 URL,再和带 hash 的产物对应,优化才有明确靶子。


6. 实验室数据:Lighthouse CI 与性能预算

6.1 为什么需要实验室数据

RUM 反映真实分布但滞后且嘈杂;实验室数据在受控环境下可复现,适合做 CI 门禁。

6.2 Lighthouse CI 配置

// lighthouserc.js
module.exports = {
  ci: {
    collect: { staticDistDir: './dist', numberOfRuns: 3 },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
      },
    },
  },
}

6.3 性能预算

- 首屏 JS ≤ 170KB(gzip)
- 首屏 CSS ≤ 30KB(gzip)
- LCP 资源 ≤ 200KB
- Lighthouse 性能分 ≥ 0.9

记忆:RUM 看真实分布、Lighthouse CI 做受控门禁——把性能预算写成 CI 断言,回归就会在合并前被拦下。


7. 构建产物体积与指标的关联

7.1 体积与 LCP 的关系

LCP 主要由首屏关键资源决定,而首屏资源大小直接来自构建产物:

# 看首屏入口 chunk 大小
ls -la dist/assets/index-*.js dist/assets/index-*.css

# 看产物构成
npx vite build --report

7.2 建立体积基线

每次发布记录:
  - 入口 JS/CSS 的 gzip 体积
  - 首屏异步 chunk 数量
  - 最大资源体积
与上一版本对比,超过阈值即告警

7.3 从指标反推优化项

指标异常优先检查
LCP 偏高入口 chunk、首屏图片、字体
INP 偏高长任务、大依赖同步执行
CLS 偏高图片尺寸、字体 swap、广告位

记忆:把体积基线和指标绑在一起看——LCP 高先查入口 chunk 与首屏资源,INP 高先查长任务,CLS 高先查尺寸与字体。


8. 上报数据的服务端处理与可视化

8.1 上报格式

{
  "version": "a1b2c3",
  "metrics": [
    { "name": "LCP", "value": 2340, "rating": "good", "url": "/home" },
    { "name": "INP", "value": 180, "rating": "good", "url": "/home" }
  ]
}

8.2 关注分位数而非均值

性能数据是长尾分布,均值会被少数极端值带偏,应看 p75 与 p95:

p75  → 大多数用户的体验
p95  → 尾部体验(易被忽略但影响口碑)

8.3 可视化维度

- 按版本对比(本次发布是否回归)
- 按页面路由对比(哪个页面最慢)
- 按设备类型对比(移动端通常更差)
- 按地理区域对比(CDN 覆盖差异)

记忆:性能数据看 p75 与 p95 而非均值——按版本、路由、设备、区域四个维度切分,才能定位到底是哪次发布、哪个页面、哪类设备在退化。


9. 常见陷阱:重复上报、采样偏差与阻塞

9.1 高频陷阱

现象原因处理
指标重复计数多次注册回调只注册一次
数据量爆炸未采样加采样率
上报丢失用了同步 XHR改 sendBeacon
指标偏低页面隐藏时未补报监听 visibilitychange
监控拖慢页面脚本阻塞主线程动态导入、延迟执行
数据不可比版本号未注入define 注入版本

9.2 采样偏差

采样必须在会话级别做,而不是每条指标各自随机——否则同一用户的部分指标上报、部分不上报,分布会被扭曲。

9.3 别让监控成为负担

监控脚本自身也要算性能成本:用动态导入延迟加载、只在生产启用、上报体量控制在几 KB。

记忆:监控翻车集中在「重复、采样、丢失、阻塞」四类——会话级采样、sendBeacon 上报、动态导入延迟执行,监控才不会变成新的性能问题。


10. 持续监控与性能回归防护

10.1 回归防护的三道闸

第一道:Lighthouse CI 断言(合并前拦截明显回归)
第二道:产物体积基线对比(构建后对比阈值)
第三道:RUM 版本对比(发布后看 p75 是否恶化)

10.2 落地清单

□ 生产环境启用 RUM,开发态自动关闭
□ 构建版本号已注入上报数据
□ 上报走 sendBeacon 且按会话采样
□ 指标在 visibilitychange 时补报
□ Lighthouse CI 已配置性能预算断言
□ 每次发布记录入口 JS/CSS 体积基线
□ 看板按版本、路由、设备、区域切分

10.3 一句话总结

采集要准(可见性补报 + 会话采样)
上报要轻(beacon + 批量)
归因要细(元素与资源 URL)
门禁要硬(CI 断言 + 体积基线)

记忆:性能监控的闭环是「采集准、上报轻、归因细、门禁硬」——RUM 看真实分布、Lighthouse 做受控门禁、体积基线守回归,三者缺一不可。


延伸阅读

  • /vite-runtime-performance-optimization/ — 运行期性能与首屏体验
  • /vite-build-optimization/ — 构建产物与体积优化
  • /vite-bundle-analysis-performance/ — 产物分析与性能预算
  • /vite-ci-cd-optimization/ — CI 流水线与性能门禁
  • /vite-env-production-best-practices/ — 生产环境最佳实践
  • 前端工程化专题 — 前端性能与工程化全景

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「vite」更多文章

  1. 从 Webpack 迁移到 Vite:配置映射、loader 与插件对应与常见坑
  2. Vite 国际化与多语言构建:按语言分包、懒加载与回退策略
  3. Vite CSS 处理架构:CSS Modules、PostCSS、Tailwind 与提取策略