教程:为 Core Web Vitals 搭建真实用户监控(RUM)

通过自定义的 RUM 实现来监控真实用户的 Core Web Vitals 数据。本教程将带你一步步构建一套可用于生产环境的 RUM 系统。

前置条件

第 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 进行对比:

验证清单