通过自定义的 RUM 实现来监控真实用户的 Core Web Vitals 数据。本教程将带你一步步构建一套可用于生产环境的 RUM 系统。
前置条件
- 具备基础的 JavaScript 知识
- 拥有一台 Web 服务器或一个 serverless 平台的访问权限
- 拥有 Google Search Console 访问权限以用于验证
第 1 步:安装 Web Vitals 库
npm install web-vitals
第 2 步:实现 RUM 数据采集
// rum.js
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';
const analyticsEndpoint = '/api/vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
url: window.location.href,
userAgent: navigator.userAgent,
connection: navigator.connection ? {
effectiveType: navigator.connection.effectiveType,
downlink: navigator.connection.downlink,
} : null,
timestamp: Date.now(),
});
// Use sendBeacon for reliability
if (navigator.sendBeacon) {
navigator.sendBeacon(analyticsEndpoint, body);
} else {
fetch(analyticsEndpoint, { body, method: 'POST', keepalive: true });
}
}
// Register all Core Web Vitals observers
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
第 3 步:服务端数据采集
// api/vitals.js (Vercel serverless function)
export default function handler(req, res) {
if (req.method !== 'POST') return res.status(405).end();
const metric = req.body;
// Validate metric data
if (!metric.name || !metric.value) {
return res.status(400).json({ error: 'Invalid metric' });
}
// Store in database
await db.query(`
INSERT INTO web_vitals (
metric_name, value, rating, delta, page_url,
user_agent, connection_type, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
`, [
metric.name, metric.value, metric.rating,
metric.delta, metric.url,
metric.userAgent, metric.connection?.effectiveType
]);
res.status(200).json({ success: true });
}
第 4 步:搭建数据看板
SELECT
page_url,
metric_name,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY value) as p75,
COUNT(*) as sample_size,
AVG(CASE WHEN rating = 'good' THEN 1 ELSE 0 END) as good_rate
FROM web_vitals
WHERE created_at > NOW() - INTERVAL '28 days'
GROUP BY page_url, metric_name
HAVING COUNT(*) > 50
ORDER BY p75 DESC;
第 5 步:设置告警
// Alert when p75 exceeds thresholds
const THRESHOLDS = {
LCP: 2500,
INP: 200,
CLS: 0.1,
FCP: 1800,
TTFB: 800,
};
async function checkVitalsAlerts() {
for (const [metric, threshold] of Object.entries(THRESHOLDS)) {
const result = await getLatestP75(metric);
if (result.p75 > threshold) {
await sendAlert(`${metric} p75 (${result.p75}ms) exceeds threshold (${threshold}ms)`);
}
}
}
第 6 步:与 Search Console 数据整合
将你的 RUM 数据与 Google Search Console 进行对比:
- 对齐 RUM 与 CrUX 之间的 URL 模式
- 找出实验室数据(lab)与真实环境数据(field)出现分歧的页面
- 基于流量 + 较差的 vitals 表现来确定优化优先级
验证清单
- Web Vitals 库已正常加载并能采集到各项指标
- 数据已成功到达服务端接口
- 数据库已正确存储各项指标
- 看板能够按页面展示 p75 数值
- 当数值超过阈值时告警能够正常触发
- RUM 数据与 Search Console 的真实环境数据相吻合
- 样本量足够(每个页面在每 28 天内有 100 条以上)
- 归因数据有助于识别出 LCP 元素