Performance Budgeting:SaaS 应用的性能预算设定与自动化执行

为 SaaS 应用实施性能预算的实践指南——涵盖指标选择、预算阈值、CI/CD 自动化执行、仪表板监控与团队协作流程。

Alex Chen
Alex Chen
June 16, 202610 min read
其他语言:Deutsch · English

TL;DR: 性能预算是应用必须遵守的一组约定阈值——涵盖资源包大小、加载时间、图片重量和 API 延迟。没有预算,性能会随着每个功能的加入而逐步退化。本文介绍如何在 CI/CD 中定义、测量和执行性能预算。


为什么性能预算很重要

性能衰减静默发生。每个功能都会增加 JavaScript、图片和 API 调用。单独来看,每次增加都不易察觉。但累积下来,6 到 12 个月后,一个初始加载 1.5 秒的页面会变成一个需要 4 秒加载的页面。

功能新增 JS新增延迟累积(6 个月)
数据分析仪表板18 KB+100ms+100ms
团队协作功能25 KB+200ms+300ms
文件上传界面12 KB+80ms+380ms
数据导出8 KB+50ms+430ms
聊天集成35 KB+300ms+730ms

没有预算,730ms 的累积增量会一直被忽视,直到用户开始抱怨——或者你的 Core Web Vitals 分数下降,SEO 排名随之滑落。


定义预算

指标分类

typescript
// performance-budget.config.ts
export const performanceBudget = {
  // 数量预算 —— 在构建时追踪
  javascript: {
    totalInitial: 150 * 1024,    // 150 KB (gzip 后)
    routeLevel: 50 * 1024,       // 每个路由分片 50 KB
  },
  css: {
    totalInitial: 30 * 1024,      // 30 KB
    criticalPath: 15 * 1024,      // 关键路径内联 15 KB
  },
  images: {
    heroImage: 200 * 1024,       // 200 KB
    thumbnail: 50 * 1024,         // 50 KB
    totalPageWeight: 500 * 1024,  // 总页面重量 500 KB
    maxImageCount: 15,            // 每页最多图片数
  },
  fonts: {
    total: 40 * 1024,            // 40 KB
    maxCustomFonts: 2,            // 最多字体家族数
  },

  // 时序预算 —— 在 CI 和 RUM 中追踪
  timing: {
    lcp: 2500,                      // < 2.5s
    cls: 0.1,                       // < 0.1
    inp: 200,                       // < 200ms
    ttfb: 800,                      // < 800ms
    firstContentfulPaint: 1800,     // < 1.8s
    timeToInteractive: 3500,         // < 3.5s
  },

  // 网络预算
  network: {
    totalRequests: 50,          // 最多 HTTP 请求数
    thirdPartyScripts: 5,        // 最多第三方脚本数
    blockingScripts: 0,          // 无渲染阻塞脚本
  },
}

使用 Lighthouse CI 执行 CI/CD 自动化

将性能预算集成到 CI 流水线中,可以确保任何未达标的 PR 都无法合并。以下 GitHub Actions 工作流在每次 Pull Request 时运行 Lighthouse CI 和资源包大小检查:

yaml
# .github/workflows/performance.yml
name: Performance Budget

on:
  pull_request:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm run build
      - run: npm run start:preview &

      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli
          lhci autorun --config=lighthouserc.json

      - name: Check Bundle Size
        run: |
          npm run build -- --report
          node scripts/check-bundle-size.mjs ./dist/report.html

      - name: Check Image Budget
        run: |
          node scripts/check-image-budget.mjs ./dist/public

      - name: Comment PR on Failure
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Performance budget check failed. See details in the workflow run.'
            })

Lighthouse CI 配置

Lighthouse CI 断言定义了每个性能指标通过或失败的标准。此配置强制执行 LCP、CLS、INP 等 Core Web Vitals 阈值,不同指标使用不同严重级别:

json
{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "staticDistDir": "./dist/public",
      "settings": {
        "throttlingMethod": "devtools",
        "formFactor": "desktop"
      }
    },
    "assert": {
      "assertions": {
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "interaction-to-next-paint": ["error", { "maxNumericValue": 200 }],
        "total-blocking-time": ["error", { "maxNumericValue": 200 }],
        "max-potential-fid": ["warn", { "maxNumericValue": 100 }],
        "uses-responsive-images": "error",
        "offscreen-images": "error",
        "unminified-javascript": "error",
        "unminified-css": "error",
        "render-blocking-resources": ["warn", { "maxLength": 3 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

资源包大小执行

JavaScript 资源包大小是可控的性能杠杆中最具影响力的之一。以下脚本将构建产物与预设大小预算进行比对,任一资源包超出限制时将导致 CI 构建失败:

typescript
// scripts/check-bundle-size.mjs
import { readFile } from "fs/promises"
import path from "path"

const BUDGET = {
  "main": 150 * 1024,      // 主入口:150KB
  "vendor": 50 * 1024,     // 供应商分片:50KB
}

async function checkBundleSize() {
  const source = path.join(process.cwd(), "dist", "report.html")
  const content = await readFile(source, "utf-8")

  const failures = []
  for (const [key, max] of Object.entries(BUDGET)) {
    const size = extractBundleSize(content, key)
    if (size && size > max) {
      failures.push(`${key}: ${formatSize(size)} > ${formatSize(max)}`)
    }
  }

  if (failures.length > 0) {
    console.error("Bundle size budget failed:")
    failures.forEach((f) => console.error(`  ${f}`))
    process.exit(1)
  }

  console.log("Bundle size budget passed")
}

预算超限时的自动化告警

除 CI 执行外,对生产环境性能数据进行实时监控,有助于发现那些仅在真实用户条件下才会暴露的性能退化。以下服务端函数查询 RUM 数据和构建产物,生成性能告警:

typescript
// server/monitoring/performance-alerts.ts
export const checkPerformanceAlert = createServerFn({ method: "GET" }).handler(
  async ({}, { context }) => {
    const results = []

    // 从 RUM 数据检查时序预算
    const vitals = await context.env.DB.prepare(`
      SELECT name, AVG(value) as avg, PERCENTILE(value, 95) as p95
      FROM web_vitals
      WHERE created_at > datetime('now', '-1 hour')
      GROUP BY name
    `).all()

    const timingBudget = {
      LCP: 2500,
      CLS: 0.1,
      INP: 200,
      TTFB: 800,
    }

    for (const row of vitals.results) {
      const threshold = timingBudget[row.name as keyof typeof timingBudget]
      if (threshold && Number(row.p95) > threshold) {
        results.push({
          type: "performance_regression",
          metric: row.name,
          p95: row.p95,
          threshold,
          severity: "warning",
        })
      }
    }

    // 从构建产物检查资源包大小
    const bundleSize = await context.env.DB.prepare(`
      SELECT route, SUM(size) as total
      FROM bundle_sizes
      WHERE version = (SELECT MAX(version) FROM bundle_sizes)
      GROUP BY route
    `).all()

    const bundleBudget = 150 * 1024 // 150KB
    const oversized = bundleSize.results.filter(
      (r) => Number(r.total) > bundleBudget
    )

    if (oversized.length > 0) {
      results.push({
        type: "bundle_size_exceeded",
        routes: oversized.map((r) => `${r.route}: ${formatSize(r.total)}`),
        severity: "warning",
      })
    }

    return results
  }
)

预算可视化仪表板

实时仪表板让团队随时掌握当前性能状态。以下 React 组件以颜色编码状态指示器展示每个指标与其预算阈值的对比:

typescript
// Dashboard component showing budget status
function PerformanceBudgetDashboard() {
  const { data: budgets } = useQuery({
    queryKey: ["performance-budget"],
    queryFn: () => checkPerformanceAlert(),
    refetchInterval: 60_000, // 每分钟刷新
  })

  return (
    <div className="grid grid-cols-3 gap-4">
      {Object.entries(timingBudget).map(([metric, budget]) => (
        <BudgetCard
          key={metric}
          metric={metric}
          current={currentValues[metric]}
          budget={budget}
        />
      ))}
    </div>
  )
}

function BudgetCard({ metric, current, budget }: BudgetCardProps) {
  const ratio = current / budget
  const status = ratio > 1 ? "failing" : ratio > 0.8 ? "warning" : "passing"

  return (
    <div className={`budget-card status-${status}`}>
      <div className="metric-name">{metric}</div>
      <div className="current-value">{formatMetric(metric, current)}</div>
      <div className="budget-bar">
        <div className="bar-fill" style={{ width: `${Math.min(ratio * 100, 100)}%` }} />
      </div>
      <div className="budget-limit">Limit: {formatMetric(metric, budget)}</div>
    </div>
  )
}

预算维护:何时调整

信号行动
预算持续失败且超幅 < 10%将预算降低 5%,与实际性能匹配
预算持续失败且超幅 > 20%追查根本原因——修复后再调整预算
预算通过且余量 > 50%收紧预算——可以更激进一些
新功能需要增加预算增加预算,同时设定优化回落日期
Core Web Vitals 排名下降追查并修复——不要通过调整预算来迁就

团队协作流程

开发者提交 PR
  → CI 运行资源包分析 + Lighthouse
    → 所有预算通过 → 自动合并开启
    → 任意预算失败 → PR 评论列出具体失败项
      → 开发者审查并优化
        → 或者:确认性能退化,添加 performance-debt 标签
        → 上线前周五进行性能评审

性能预算实施清单

  • 定义 JS(首屏)、CSS 和各路由分片的资源包大小预算
  • 定义图片预算(总页面重量、最多图片数)
  • 定义时序预算(LCP、CLS、INP、TTFB)
  • CI 流水线在每次 PR 时执行预算检查
  • 构建产物中集成资源包分析器
  • 每次 PR 运行 Lighthouse CI 并配置断言失败
  • 在监控工具中配置性能预算仪表板
  • 生产环境预算超限时发送自动化告警
  • 每季度审查并调整预算
  • 在 GitHub 中为已确认的性能退化添加标签

结语

性能预算不是为了追求完美——而是为了防止性能静默退化。通过定义明确的阈值并在 CI 中强制执行,你可以在性能退化进入生产环境之前就将其捕获。

核心洞察:每个功能都有性能成本。在代码审查阶段而非用户投诉之后承认这一成本,将性能从亡羊补牢转变为第一优先级的设计约束。

TanStack Start 较小的基础资源包大小(约 85 KB)为性能建设提供了良好起点,但真正的性能表现需要持续的执行纪律。如需了解更多基础指标和优化技术,请参阅我们的 Core Web Vitals 优化指南、真实用户监控(用于生产环境测量)、Lighthouse 95+ 优化技巧(审计最佳实践)以及 边缘缓存策略(CDN 层性能提升)。