Lighthouse 分数从 45 提到 95:实战 7 个改动

起因

新 landing page 在 Lighthouse Mobile 上跑只有 45 分(红色)。
boss 看到 PageSpeed Insights 截图骂街。
"哪些指标拉低分数?怎么改?" 系统化地走一遍。

6 个关键指标

Lighthouse 综合分基于:

指标 解释 目标
LCP (Largest Contentful Paint) 首屏最大元素显示时间 < 2.5s
INP (Interaction to Next Paint) 用户交互响应延迟 < 200ms
CLS (Cumulative Layout Shift) 累计布局偏移 < 0.1
FCP (First Contentful Paint) 任何内容首次显示 < 1.8s
Speed Index 视觉填充速度 < 3.4s
TBT (Total Blocking Time) 主线程被阻塞总时长 < 200ms

LCP / INP / CLS 是 "Core Web Vitals",Google 排名因子。

我做的 7 个改动

1. 给关键图片加 fetchpriority="high" 和 preload

<head>
  <link rel="preload" as="image" href="/hero.jpg" fetchpriority="high">
</head>
<body>
  <img src="/hero.jpg" fetchpriority="high" loading="eager"
       width="1200" height="600" alt="...">
</body>

之前 hero 图被各种 CSS / JS 排到后面,LCP 4s。
preload + high priority 后 1.2s。

2. 字体 preload + font-display: swap

<link rel="preload" as="font" type="font/woff2"
      href="/fonts/inter.woff2" crossorigin>

<style>
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;   /* 字体没下来用 fallback,不阻塞渲染 */
}
</style>

之前自定义字体阻塞文字渲染 800ms。改后字体没到先用 system fallback。

3. 关闭未使用的第三方脚本

PageSpeed 的 "Reduce unused JavaScript" 报告告诉我 Google Analytics +
Hotjar + Intercom 注入了 280KB JS。

  • Hotjar 临时关(运营暂时不需要)
  • Intercom 改 "页面停留 > 10s 才加载"
  • GA 切到 gtag.js(最小版)

JS bundle 从 320 KB → 80 KB。

4. CSS critical path:inline above-the-fold + defer 其余

<head>
  <style>
    /* 首屏关键 CSS inline 进 HTML */
    body{font-family:system-ui;...} .hero{...} .nav{...}
  </style>
  <link rel="preload" href="/main.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/main.css"></noscript>
</head>

之前 main.css 阻塞 render 600ms。inline critical CSS 后渲染立刻开始。

工具:critters / critical npm 包能自动提取 critical CSS。

5. 图片用 AVIF / WebP + responsive srcset

<picture>
  <source srcset="/hero-800.avif 800w, /hero-1600.avif 1600w"
          sizes="100vw" type="image/avif">
  <source srcset="/hero-800.webp 800w, /hero-1600.webp 1600w"
          sizes="100vw" type="image/webp">
  <img src="/hero-800.jpg" alt="" width="1600" height="800"
       loading="eager" fetchpriority="high">
</picture>

AVIF 比 JPEG 小 50%+。手机用 800w 版本,平板用 1600w。
图片总下载量从 1.2 MB → 300 KB。

6. 移除阻塞渲染的 JS(defer / async)

<!-- 之前 -->
<script src="/analytics.js"></script>     <!-- 阻塞 -->
<script src="/vendor.js"></script>         <!-- 阻塞 -->

<!-- 现在 -->
<script src="/main.js" defer></script>     <!-- 等 HTML parse 完才执行 -->
<script src="/analytics.js" async></script><!-- 不阻塞,下载完就跑 -->
  • defer:HTML parse 完 + DOMContentLoaded 前按顺序执行
  • async:下载完立刻执行,不保顺序
  • 不带:阻塞 parser(最差)

7. CLS:所有 image / iframe 写 width/height

<!-- 错 -->
<img src="/photo.jpg" alt="">    <!-- 加载完撑高页面 → 跳动 -->

<!-- 对 -->
<img src="/photo.jpg" width="800" height="600" alt="">

让浏览器在图片下载完前知道占位空间。CSS 用 max-width:100%; height:auto
保持响应式。

CLS 从 0.34 → 0.02。

跑 Lighthouse

Chrome DevTools → Lighthouse → 选 Mobile + Performance → Generate report.

或者命令行 CI 跑:

npm i -D lighthouse @lhci/cli
lhci autorun --upload.target=temporary-public-storage

@lhci/cli 能跑多次取中位数 + 上传报告 + PR 评论分数变化。

结果

改前 改后
Performance 45 95
LCP 4.2s 1.4s
INP 380ms 120ms
CLS 0.34 0.02
TBT 850ms 110ms
JS bundle 320 KB 80 KB
首屏图片 1.2 MB 300 KB

持续优化

CI 限红线

# lighthouse.config.js
module.exports = {
  ci: {
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
      },
    },
  },
}

LCP > 2.5s 的 PR 直接 fail。防止性能慢慢退化。

真实用户数据:web-vitals + analytics

import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals'

const send = (metric) => {
  navigator.sendBeacon('/analytics/vitals', JSON.stringify({
    name: metric.name,
    value: metric.value,
    id: metric.id,
  }))
}

onCLS(send)
onINP(send)
onLCP(send)
onFCP(send)
onTTFB(send)

收集真实用户的 web vitals 上报到自己的分析后端。Lighthouse 是合成测试,
真实用户在各种设备 / 网络下的体验才是终极目标。

踩过的坑

  1. 测试环境 vs 生产:dev server 没 gzip / 没缓存 → 跑 Lighthouse
    分数极低。永远在 staging / prod-like build 上跑。

  2. fetchpriority="high" 滥用:所有 img 都设 high → 优先级失效。
    只首屏关键 1-2 个图设。

  3. inline critical CSS 太多:> 14 KB(一个 TCP 窗口)反而慢,因为
    阻塞了 HTML 后续 stream。生产建议 < 10 KB。

  4. Hydration 慢拉低 INP:React app 大 bundle hydrate 时主线程被
    占。RSC + 把交互组件拆细减少 hydration scope。

  5. PSI mobile 分数比 Desktop 低很多:移动用 throttled CPU + 慢 4G。
    永远以 mobile 为准(用户主要在手机)。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。