2025年10月05日10 分钟

Web 性能指标优化实战:FCP / LCP / TTI / FID / CLS / TTFB

逐个拆解六大 Web 性能指标的含义、测量方法和具体优化方案,用数据驱动性能提升

指标全景

用户请求 → 服务器响应 → 首次渲染 → 内容可见 → 可交互 → 布局稳定
    │          │           │          │         │         │
    └─ TTFB ──┘    FCP ───┘   LCP ───┘  TTI ──┘  CLS ──┘
                                          FID ──┘

1. TTFB — 服务器响应时间

Time to First Byte:从请求发出到收到第一个字节的时间。

目标:< 800ms(Good)
        800-1800ms(Needs Improvement)
        > 1800ms(Poor)

测量范围:
  DNS 解析 + TCP 连接 + TLS 握手 + 服务器处理 + 网络传输

优化方案

① 使用 CDN(最直接)
  用户 → 最近的 CDN 节点(5-50ms)
  而不是 → 远端源服务器(200-500ms)

② 服务端缓存
  // Next.js ISR
  export const revalidate = 3600  // 缓存 1 小时
  // 命中缓存时 TTFB < 50ms

③ 数据库查询优化
  SELECT * FROM posts → 500ms  ❌
  SELECT title, date FROM posts WHERE id = ? → 5ms  ✅
  + 添加索引 + 连接池

④ 流式 SSR(React 18)
  传统 SSR: 等所有数据 → 生成完整 HTML → TTFB 高
  流式 SSR: 先发 HTML 外壳 → TTFB 低 → 内容逐步推送

⑤ Edge Computing
  Vercel Edge Functions / Cloudflare Workers
  代码在 CDN 边缘运行 → TTFB 极低

2. FCP — 首次内容绘制

First Contentful Paint:浏览器首次渲染任何文本、图片或 SVG 的时间。

目标:< 1.8s

时间线:
  请求 → 白屏 → [FCP] 首个内容出现 → 完整内容加载

优化方案

① 消除渲染阻塞资源
  <!-- ❌ 阻塞渲染 -->
  <link rel="stylesheet" href="/large-styles.css">
  <script src="/analytics.js"></script>

  <!-- ✅ 异步加载非关键资源 -->
  <link rel="stylesheet" href="/critical.css">
  <link rel="stylesheet" href="/non-critical.css" media="print" onload="this.media='all'">
  <script src="/analytics.js" defer></script>

② 内联关键 CSS
  <head>
    <style>
      /* 首屏需要的关键 CSS 直接内联 */
      body { margin: 0; font-family: sans-serif; }
      .hero { min-height: 100vh; }
    </style>
    <!-- 完整 CSS 异步加载 -->
    <link rel="preload" href="/styles.css" as="style" onload="this.rel='stylesheet'">
  </head>

③ 使用 next/font 避免字体阻塞
  // font-display: swap → 先用系统字体,自定义字体加载后切换
  const inter = Inter({ subsets: ['latin'], display: 'swap' })

④ 减小 HTML 体积
  SSR 输出精简 HTML → 更快到达 → 更快解析 → 更快 FCP

⑤ 预加载关键资源
  <link rel="preload" href="/hero-image.webp" as="image">
  <link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin>

3. LCP — 最大内容绘制

Largest Contentful Paint:最大可见内容元素完成渲染的时间。

目标:< 2.5s

LCP 元素通常是:
  - 大图片(hero banner)
  - 大块文本
  - 视频封面
  - 大型 SVG

优化方案

① 优化 LCP 元素的加载
  // 图片:使用 Next.js Image + priority
  <Image src="/hero.jpg" priority sizes="100vw" />
  // priority 会添加 <link rel="preload">

  // 或手动 preload
  <link rel="preload" as="image" href="/hero.webp"
    imagesrcset="/hero-400.webp 400w, /hero-800.webp 800w"
    imagesizes="100vw" />

② 图片格式优化
  JPEG: 100KB → WebP: 70KB → AVIF: 50KB
  <picture>
    <source srcset="/hero.avif" type="image/avif">
    <source srcset="/hero.webp" type="image/webp">
    <img src="/hero.jpg" alt="Hero">
  </picture>

③ 响应式图片(避免加载过大图片)
  <img
    srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
    sizes="(max-width: 768px) 100vw, 50vw"
    src="hero-800.webp"
  />
  // 手机加载 400px 图,不加载 1200px 的大图

④ 如果 LCP 是文本 → 优化字体加载
  font-display: swap + preload 字体文件

⑤ SSR / SSG 让内容直接在 HTML 中
  CSR: HTML 空 → JS 下载执行 → 渲染内容 → LCP(慢)
  SSR: HTML 含内容 → 直接渲染 → LCP(快)

4. FID — 首次输入延迟

First Input Delay:用户首次交互到浏览器开始处理的延迟。

目标:< 100ms

用户点击按钮
     │
     ├─ 主线程空闲 → 立即处理 → FID ≈ 0ms ✅
     │
     └─ 主线程正忙(执行 JS)→ 等待 → 才能处理 → FID = 等待时间 ❌

优化方案

① 减少主线程长任务
  // ❌ 一次性处理大量数据
  function processData() {
    for (let i = 0; i < 100000; i++) { /* 耗时计算 */ }
    // 主线程阻塞 200ms → FID 200ms
  }

  // ✅ 分片处理
  function processDataChunked(data) {
    const CHUNK_SIZE = 1000
    let index = 0

    function processChunk() {
      const end = Math.min(index + CHUNK_SIZE, data.length)
      for (let i = index; i < end; i++) { /* 处理 */ }
      index = end

      if (index < data.length) {
        // 让出主线程,允许处理用户交互
        requestIdleCallback(processChunk)
      }
    }
    processChunk()
  }

② 代码分割减少首屏 JS
  // 首屏不需要的模块延迟加载
  const HeavyModule = lazy(() => import('./HeavyModule'))

③ Web Worker 处理 CPU 密集任务
  // worker.js
  self.onmessage = (e) => {
    const result = heavyComputation(e.data)
    self.postMessage(result)
  }

  // main.js
  const worker = new Worker('./worker.js')
  worker.postMessage(data)  // 不阻塞主线程

④ 第三方脚本延迟加载
  <!-- ❌ 同步加载 → 阻塞主线程 -->
  <script src="https://analytics.com/heavy.js"></script>

  <!-- ✅ defer/async + 延迟初始化 -->
  <script src="https://analytics.com/heavy.js" defer></script>

5. CLS — 累积布局偏移

Cumulative Layout Shift:页面生命周期内所有意外布局偏移的累积分数。

目标:< 0.1

什么导致 CLS:
  ① 图片/视频没有设置尺寸 → 加载后撑开
  ② 字体加载导致文字大小变化(FOUT)
  ③ 动态插入内容(广告、弹窗)
  ④ 异步加载的组件导致页面跳动

优化方案

① 图片/视频始终设置尺寸
  <!-- ❌ 没有尺寸 → 加载前高度 0 → 加载后撑开 → CLS -->
  <img src="/photo.jpg">

  <!-- ✅ 设置宽高 → 浏览器预留空间 -->
  <img src="/photo.jpg" width="800" height="600">

  <!-- ✅ Next.js Image 自动处理 -->
  <Image src="/photo.jpg" width={800} height={600} />

  <!-- ✅ CSS aspect-ratio -->
  <div style="aspect-ratio: 16/9">
    <img src="/photo.jpg" style="width: 100%; height: 100%; object-fit: cover">
  </div>

② 字体优化避免 FOUT
  // font-display: optional → 100ms 内没加载完就一直用系统字体
  @font-face {
    font-family: 'MyFont';
    font-display: optional;  /* 不会导致文字重排 */
    src: url('/fonts/myfont.woff2');
  }

  // 或 preload 确保尽快加载
  <link rel="preload" href="/fonts/myfont.woff2" as="font" crossorigin>

③ 为动态内容预留空间
  // ❌ 广告加载后把内容往下推
  <div>{ad && <AdBanner />}</div>  // 从 0 高度变成 250px

  // ✅ 提前预留空间
  <div style={{ minHeight: 250 }}>
    {ad ? <AdBanner /> : <Skeleton height={250} />}
  </div>

④ 使用 transform 做动画
  /* ❌ 改变布局属性 → 导致重排 → CLS */
  .animate { top: 100px; left: 50px; }

  /* ✅ transform 不影响布局 → 不产生 CLS */
  .animate { transform: translate(50px, 100px); }

6. TTI — 完全可交互时间

Time to Interactive:页面完全可以响应用户交互的时间。

目标:< 3.8s

TTI 的判定标准:
  1. FCP 已完成
  2. 主要的事件处理器已注册
  3. 主线程连续 5 秒内没有长任务(> 50ms)

优化方案

① 减少 JS Bundle 体积(最直接)
  体积小 → 下载快 → 解析快 → 执行快 → TTI 低

② 延迟非关键 JS
  // 首屏不需要的功能延迟加载
  // 聊天窗口、反馈按钮、分析脚本等
  if ('requestIdleCallback' in window) {
    requestIdleCallback(() => {
      import('./analytics').then(m => m.init())
      import('./chat-widget').then(m => m.mount())
    })
  }

③ Progressive Hydration(渐进式水合)
  // React 18 Suspense 实现
  <Suspense fallback={<Skeleton />}>
    <HeavyComponent />  // 延迟水合,不阻塞 TTI
  </Suspense>

④ 使用 Service Worker 预缓存
  // 第二次访问时关键资源直接从缓存读取
  // JS 文件零网络延迟 → TTI 大幅降低

测量工具

实验室数据(Lab):
  ├── Lighthouse(Chrome DevTools → Lighthouse 面板)
  ├── WebPageTest(webpagetest.org)
  └── PageSpeed Insights(pagespeed.web.dev)

真实用户数据(Field / RUM):
  ├── Chrome UX Report (CrUX)
  ├── web-vitals 库(自行采集)
  └── 第三方 RUM 平台

推荐工作流:
  1. Lighthouse CI 集成到 CI/CD → 每次构建检查
  2. web-vitals 库采集真实用户数据 → 上报分析
  3. Chrome DevTools Performance 面板 → 定位具体问题

总结速查表

指标目标核心优化
TTFB< 800msCDN + 缓存 + Edge Computing
FCP< 1.8s消除渲染阻塞 + 内联关键 CSS + SSR
LCP< 2.5s预加载 LCP 元素 + 图片优化 + SSR
FID< 100ms减少长任务 + 代码分割 + Web Worker
CLS< 0.1设置图片尺寸 + 预留空间 + transform 动画
TTI< 3.8s减小 JS 体积 + 延迟非关键脚本