📚 现代前端开发 目录
阶段5:全栈与部署
第 34 / 35 课

性能监控

量化与优化应用性能

📖 核心概念

💻 代码实现

Web Vitals监控 ✅ typescript
// Web Vitals上报
import { onCLS, onFID, onLCP, onFCP, onTTFB, onINP } from 'web-vitals';

type MetricName = 'CLS' | 'FID' | 'LCP' | 'FCP' | 'TTFB' | 'INP';

interface WebVitalMetric {
  name: MetricName;
  value: number;
  rating: 'good' | 'needs-improvement' | 'poor';
  delta: number;
  navigationType: string;
}

function sendToAnalytics(metric: WebVitalMetric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    delta: metric.delta,
    id: metric.name.toLowerCase() + '-' + Date.now(),
    url: window.location.href,
    userAgent: navigator.userAgent,
  });

  // 使用sendBeacon确保页面卸载时也能发送
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/analytics/web-vitals', body);
  } else {
    fetch('/api/analytics/web-vitals', { body, method: 'POST', keepalive: true });
  }
}

// 注册所有指标
export function reportWebVitals() {
  onCLS(sendToAnalytics);
  onFID(sendToAnalytics);
  onLCP(sendToAnalytics);
  onFCP(sendToAnalytics);
  onTTFB(sendToAnalytics);
  onINP(sendToAnalytics);
}

// app/layout.tsx
// import { ReportWebVitals } from '@/components/ReportWebVitals';
// <ReportWebVitals />

// API路由:接收并存储指标
// app/api/analytics/web-vitals/route.ts
export async function POST(request: Request) {
  const metric = await request.json();
  console.log('[Web Vitals]', metric.name, metric.value, metric.rating);
  // 存储到数据库或发送到监控服务
  return new Response('OK', { status: 200 });
}
错误追踪与Sentry ✅ typescript
// Sentry集成
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1, // 10%的请求追踪
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0, // 出错时100%录制
  integrations: [
    Sentry.replayIntegration(),
    Sentry.browserTracingIntegration(),
  ],
  // 性能监控
  tracePropagationTargets: ['localhost', /^https:\/\/myapp\.com/],
  // 忽略已知错误
  ignoreErrors: ['ResizeObserver loop', 'Network request failed'],
});

// 自定义错误边界
'use client';
import { Component, type ReactNode } from 'react';
import * as Sentry from '@sentry/nextjs';

interface Props { children: ReactNode; fallback?: ReactNode; }
interface State { hasError: boolean; error?: Error; }

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    Sentry.captureException(error, { contexts: { react: { componentStack: errorInfo.componentStack } } });
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-boundary">
          <h2>出了点问题</h2>
          <button onClick={() => { this.setState({ hasError: false }); window.location.reload(); }}>
            刷新页面
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// 性能预算检查
const performanceBudget = {
  LCP: { good: 2500, poor: 4000 },   // ms
  FID: { good: 100, poor: 300 },     // ms
  CLS: { good: 0.1, poor: 0.25 },    // score
  FCP: { good: 1800, poor: 3000 },   // ms
  TTFB: { good: 800, poor: 1800 },   // ms
  bundleSize: { good: 200, poor: 400 }, // KB
};

function checkBudget(metric: string, value: number) {
  const budget = performanceBudget[metric];
  if (!budget) return 'info';
  if (value <= budget.good) return '✅ good';
  if (value <= budget.poor) return '⚠️ needs improvement';
  return '❌ poor';
}

🔍 性能监控实战经验

监控的目的是发现问题和验证优化效果,而不是追求数字。关键经验:关注P75和P95而非平均值(平均值掩盖长尾问题)、区分首次加载和二次访问、注意移动端与桌面端的差异、监控要持续而非一次性。建立性能基线,在基线上做回归检测。

🎯 练习任务

🏆 成就解锁
性能监控师 — 掌握前端性能监控体系,用数据驱动优化