Lighthouse 95+ 优化:SaaS 性能案例研究

如何将 SaaS 应用从 Lighthouse 60 优化到 95+。学习消除渲染阻塞、优化字体和用代码示例与真实数据改进 Core Web Vitals 的具体技术。

Huifer
Huifer
July 22, 202612 min read
其他语言:Deutsch · English

本文由 Huifer 撰写,TanStack Ship 的独立开发者兼维护者。 我用三个月时间将 TanStack Ship 营销网站的 Lighthouse 分数从 60 优化到 95+。这篇文章详细记录了我所做的具体工作和实际有效的方案。我分享了具体的技术手段、指标变化以及过程中犯的错误。

可靠来源:Web.dev Core Web Vitals · TanStack Ship GitHub 最后更新:2026-07-22 · 更新日志


TL;DR: 达到 Lighthouse 95+ 需要消除渲染阻塞资源、优化字体并实施 Core Web Vitals 最佳实践。以下是我遵循的具体流程。

你将学到:

  • 渲染阻塞资源消除 - 移除 20+ 个阻塞的 CSS/JS 文件
  • 字体优化策略 - 消除字体加载带来的布局偏移
  • 代码分割模式 - 将初始包体积减少 60%
  • 图片优化管道 - 将 LCP 缩短 3 秒
  • 缓存架构 - 实施正确的 CDN 和浏览器缓存

适用人群: 正在优化生产级应用的 SaaS 开发者。需要具备网页性能指标的基础知识和构建工具配置的动手能力。


目录

  1. 起点 - 基准指标和根因分析
  2. 性能审计 - 识别瓶颈的工具和技术
  3. 流式 SSR - 含代码示例的实现方法
  4. 字体优化 - 延迟加载策略
  5. 代码分割 - 基于路由和基于组件的分割
  6. 图片优化 - 现代格式和响应式图片
  7. 关键 CSS 内联 - 提取并内联关键 CSS
  8. 缓存和 CDN - 浏览器缓存和 CDN 配置
  9. 结果 - 优化前后指标和影响
  10. 常见问题 - 关于 Lighthouse 优化的常见问题

起点

第一次对 TanStack Ship 营销网站运行 Lighthouse 时,分数是 60。性能是红色。LCP 是 4.2 秒。网站能正常工作,但感觉慢。

基准指标(优化前)

  • 性能评分:60/100
  • LCP(最大内容绘制):4.2s ❌(目标:<2.5s)
  • FID(首次输入延迟):180ms ✅(目标:<100ms)
  • CLS(累积布局偏移):0.15 ❌(目标:<0.1)
  • 总阻塞时间:1,200ms ❌
  • 渲染阻塞资源:23 个文件 ❌

根本原因是可预测的:渲染阻塞的 CSS、未优化的字体和没有缓存策略。网站加载了 23 个渲染阻塞资源。字体没有被预加载。也没有 Service Worker。

我设定的目标是所有指标达到 95+。工作花了三个月专注的努力。这篇文章记录了有效的方案。

使用的工具

  • Lighthouse - 内置 Chrome DevTools 性能审计
  • WebPageTest - 详细瀑布图分析和胶片条视图
  • Chrome DevTools Coverage - 识别未使用的 CSS 和 JavaScript
  • web-vitals - 真实用户监控库

我从测量当前状态开始。在无痕窗口中运行 Lighthouse 以获得无偏结果。还使用 WebPageTest 做更详细的瀑布图分析。

审计还揭示了其他几个问题。第三方脚本同步加载。关键源站没有资源提示。生产环境中 HTML 没有压缩。这些问题加重了核心问题。

我创建了一个性能预算来指导工作。预算定义了关键指标的最大大小:HTML 50KB 以下、CSS 20KB 以下、JavaScript 150KB 以下、图片 100KB 以下。每个变更都要对照这个预算进行评估。

预算帮助我优先处理工作。我专注于最大程度推动指标的变更。有些优化花了几个小时但对指标的改善微乎其微。另一些只花了几分钟却产生了很大的差异。

性能审计:找到瓶颈

优化之前,我需要了解什么拖慢了速度。Lighthouse 的诊断指向了几个问题。

步骤 1:运行基准审计

bash
# 在 Chrome DevTools 中运行 Lighthouse
# 或使用 CLI 进行自动化
npx lighthouse https://your-site.com --output html --output-path ./audit-report.html

步骤 2:分析关键渲染路径

**最大内容绘制(LCP)**是一张未优化的首图。该图片没有被预加载,且文件大小不必要地达到了 800KB。压缩成 WebP 后,大小降至 120KB,视觉质量没有明显损失。

步骤 3:测量布局偏移

**首次输入延迟(FID)是可接受的,但累积布局偏移(CLS)**因字体交换而有问题。当 Web 字体加载时,会改变文本尺寸,导致元素移动。用户点击的按钮在字体交换过程中发生了位移。

我使用 Chrome DevTools 来识别关键渲染路径。关键路径是浏览器渲染内容之前必须加载的资源序列。关键路径越短,渲染越快。

步骤 4:识别阻塞资源

审计发现 23 个 CSS 和 JavaScript 文件阻塞了渲染。每个阻塞资源都会延迟首次绘制。最小化这些资源是第一个优化步骤。

步骤 5:找出未使用的代码

我使用 Chrome DevTools 的 Coverage(覆盖率)标签来识别未使用的 CSS 和 JavaScript。移除未使用的代码显著减少了打包大小。我还分析了导入项,以识别不需要但体积很大的依赖项。

LCP 是一张未优化的首图。没有预加载,文件大小不必要地达到了 800KB。压缩成 WebP 后,大小降至 120KB,视觉质量没有明显损失。

FID 是可接受的,但 CLS 因字体交换而有问题。当 Web 字体加载时,会改变文本尺寸,导致元素移动。用户点击的按钮在字体交换过程中发生了位移。

我使用 Chrome DevTools 来识别关键渲染路径。关键路径是浏览器渲染内容之前必须加载的资源序列。关键路径越短,渲染越快。

审计发现 23 个 CSS 和 JavaScript 文件阻塞了渲染。每个阻塞资源都会延迟首次绘制。最小化这些资源是第一个优化步骤。

我使用 Chrome DevTools 的 Coverage 标签来识别未使用的 CSS 和 JavaScript。移除未使用的代码显著减少了打包大小。我还分析了导入项,以识别不需要但体积很大的依赖项。

我还发现第三方脚本同步加载。聊天组件和统计脚本在页面加载期间阻塞了主线程。改为异步加载显著改善了 FID。我对非关键的页面渲染第三方脚本使用了 async 属性。

流式 SSR:消除渲染阻塞

TanStack Start 原生支持流式 SSR。流式传输将 HTML 增量发送到浏览器,允许浏览器在内容到达时逐步渲染,而不是等待整个响应。

流式传输对动态内容页面特别有效。浏览器可以在服务器获取数据的同时开始渲染。用户更快看到内容,这改善了感知性能。

使用 TanStack Start 实现

tsx
// routes/index.tsx
import { createRoute } from '@tanstack/react-router'

export const Route = createRoute({
  component: IndexPage,
  loader: async () => {
    // 增量流式传输数据
    const [posts, featured] = await Promise.all([
      fetchPosts(),
      fetchFeaturedPost()
    ])
    return { posts, featured }
  }
})

实现 Suspense 边界

tsx
// 用 Suspense 包裹动态内容
import { Suspense } from 'react'

function IndexPage() {
  return (
    <div>
      <StaticHeader />
      <Suspense fallback={<LoadingSkeleton />}>
        <DynamicContent />
      </Suspense>
    </div>
  )
}

Suspense 允许浏览器立即渲染静态内容,同时动态内容加载。这使首次内容绘制改善了 800ms。

结果

  • 首次内容绘制:改善 800ms
  • 可交互时间:改善 1.2s
  • 感知性能:显著更好

流式传输对动态内容页面特别有效。浏览器可以在服务器获取数据的同时开始渲染。用户更快看到内容,这改善了感知性能。

配置很简单。在路由 loader 中,尽可能增量返回数据。框架自动处理流式传输。

我实现了围绕动态内容的 Suspense 边界。Suspense 允许浏览器立即渲染静态内容,同时动态内容加载。这使首次内容绘制改善了 800ms。

我还使用了带流式传输的服务器端渲染,结合 SSR 和渐进式渲染的优势。浏览器增量接收 HTML,在内容到达时渲染。

字体优化:延迟加载

Web 字体经常在加载时引起布局偏移。浏览器先用回退字体测量文本,然后才交换到 Web 字体。这个交换导致布局移动,造成 CLS。

技术 1:使用 font-display: swap

css
/* 告诉浏览器在 Web 字体加载前使用回退字体 */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap; /* 立即使用回退字体 */
}

font-display: swap 告诉浏览器在 Web 字体加载前使用回退字体。交换是可见的,但不会导致布局偏移。

技术 2:预加载关键字体

html
<!-- 预加载关键字体 -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>

预加载告诉浏览器在页面加载过程早期下载字体。这确保字体在需要时可用。

技术 3:对非关键字体使用 font-display: optional

css
@font-face {
  font-family: 'Inter';
  font-display: optional; /* 如果未缓存则使用回退字体 */
}

font-display: optional 告诉浏览器如果 Web 字体未缓存则使用回退字体,完全避免布局偏移。用户看到本地字体,比等待 Web 字体更快。

技术 4:精简字体

bash
# 精简字体只包含使用的字符
pyftsubset Inter-Regular.ttf --text-file=characters.txt --output-file=Inter-subset.woff2

精简将字体大小减少了 40%。完整拉丁字母表比完整字体文件小得多。

技术 5:使用可变字体

css
/* 可变字体在一个文件中提供多种变化 */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2-variations');
  font-weight: 100 900; /* 所有字重在一个文件里 */
}

可变字体在一个文件中提供多种字体变化(字重、宽度、样式)。这比分别为每种变化加载单独的字体文件更高效。

我实施了几项字体优化。首先,使用 font-display: swap 控制字体加载行为。这告诉浏览器在 Web 字体加载前使用回退字体。交换是可见的,但不会导致布局偏移。

其次,使用 link rel="preload" 预加载关键字体。预加载告诉浏览器在页面加载过程早期下载字体。这确保字体在需要时可用。

第三,对非关键字体使用 font-display: optional。这告诉浏览器如果 Web 字体未缓存则使用回退字体,完全避免布局偏移。用户看到本地字体,比等待 Web 字体更快。

第四,精简字体只包含网站上使用的字符。完整拉丁字母表比完整字体文件小得多。精简将字体大小减少了 40%。

我还尽可能使用可变字体。可变字体在一个文件中提供多种字体变化(字重、宽度、样式)。这比分别为每种变化加载单独的字体文件更高效。

代码分割和延迟加载

JavaScript 包可以分割成按需加载的小块。代码分割减少了初始打包大小,改善了可交互时间。

TanStack Start 基于路由自动处理代码分割。每个路由的 JavaScript 只在该路由被访问时才加载。这是基于路由的代码分割。用户只下载需要的 JavaScript。

对于不需要立即加载的组件,延迟加载将导入推迟到它们渲染时。例如,模态组件不需要加载直到用户点击打开它的按钮。

实现使用动态导入。动态导入的组件被分割成单独的文件块。框架只在组件渲染时加载这些文件块。

我还实现了路由预加载。当用户悬停在链接上时,浏览器预加载目标路由。到用户点击时,路由已准备好渲染。

我使用了 TanStack Start 内置的预取功能。当链接出现在视口时,路由器预取路由,确保导航快速。

图片优化

图片经常造成最大的 LCP 延迟。优化图片对良好的 Core Web Vitals 至关重要。

我实施了几项图片优化。首先,使用 WebP 和 AVIF 等现代格式替代 JPEG 和 PNG。这些格式在相同质量下提供更好的压缩。AVIF 对照片特别有效,与 JPEG 相比将大小减少 50%。

其次,添加使用 srcset 的响应式图片。这允许浏览器为视口下载合适尺寸,减少小屏幕的带宽。移动端用户不需要 2000px 宽的图片。

第三,对首屏下方图片实施懒加载。不立即可见的图片不需要加载直到用户滚动到它们。这显著减少了初始页面重量。

第四,使用 link rel="preload" 预加载首图。预加载确保首图在页面生命周期中尽可能早地加载。这将 LCP 改善了 600ms。

我还给图片标签添加了 width 和 height 属性。这告诉浏览器图片尺寸在图片加载之前,防止布局偏移。

关键 CSS 内联

关键 CSS 是渲染首屏内容所需的 CSS。内联关键 CSS 消除了渲染阻塞 CSS 的网络往返。

我使用构建工具提取关键 CSS。关键 CSS 被内联在 HTML head 中,而非关键 CSS 异步加载。这叫做关键 CSS 模式。

结果是更快的首次绘制。浏览器不需要等待外部 CSS 文件加载就可以渲染内容。首次绘制改善了 400ms。

我使用工具在构建时自动提取关键 CSS。该工具识别首屏使用的 CSS 并只提取该 CSS。非关键 CSS 使用 media="print" onload="this.media='all'" 异步加载。

我还将小型关键 CSS 直接内联在 HTML head 中。这完全消除了关键 CSS 的网络往返,进一步改善了首次绘制。

缓存和 CDN 配置

缓存减少重复下载。静态资源应该被积极缓存。我为不同资源类型配置了缓存头。

HTML 页面缓存时间短或不缓存。这确保用户在更改时能看到更新后的内容。我对 HTML 使用 Cache-Control: no-cache 以确保新鲜度。

JavaScript、CSS 和图片等静态资源缓存时间长。我对哈希过的资源使用 Cache-Control: public, max-age=31536000, immutable。这告诉浏览器永久缓存。

缓存破坏使用文件名中的内容哈希。当文件更改时,其哈希值也更改,创建新的 URL。新 URL 绕过缓存,确保用户下载更新后的资源。Tangled 自动确保文件名包含内容哈希。

CDN 分发通过从边缘位置提供内容来减少延迟。TanStack Ship 部署到 Cloudflare Workers,自动提供全球 CDN 分发。内容从离每个用户最近的边缘位置提供。

我还为可能的导航路径实现了预加载。当用户进入首页时,浏览器预加载最常见的下一个页面。这使导航感觉即时。

结果:前后对比

实施这些优化后,Lighthouse 分数从 60 提高到 97。LCP 从 4.2 秒降至 1.4 秒。CLS 从 0.15 降至 0.02。网站现在平均加载时间在 2 秒以下。

指标对比

指标优化前优化后改善
性能评分60/10097/100+62% ✅
LCP4.2s1.4s-67% ✅
FID180ms45ms-75% ✅
CLS0.150.02-87% ✅
阻塞资源23 个文件3 个文件-87% ✅
可交互时间5.8s1.9s-67% ✅

发生了什么变化

改进来自系统地解决根本原因:

  • 渲染阻塞资源从 23 个减少到 3 个
  • 字体加载不再导致布局偏移
  • 图片渐进加载不阻塞渲染
  • JavaScript 包按路由分割
  • 实施了正确的缓存头
  • 通过 Cloudflare Workers 实现 CDN 分发

本文描述的技术现在是 TanStack Ship 默认配置的一部分。新项目开箱即用达到 Lighthouse 95+。无需额外工作就能获得性能优化。

持续监控

性能优化是一个持续的过程。继续监控 Core Web Vitals 并在发布前解决回归问题。在 CI 管道中设置自动化 Lighthouse 检查,在性能回归到达生产环境前捕获它们。

bash
# 添加到 CI 管道
npx lighthouse https://your-site.com --throttling-method=devtools --only-categories=performance

性能投资的回报是用户满意度和 SEO。用户在快速网站上停留更长时间。搜索引擎优先排名快速网站。努力是值得的。

我还设置了真实用户监控来追踪生产中的 Core Web Vitals。Lighthouse 中的合成测试很有用,但真实用户数据揭示实际用户体验。我使用 web-vitals 库来捕获现场数据。

javascript
// 真实用户监控设置
import { onCLS, onFID, onLCP } from 'web-vitals'

onCLS(console.log)
onFID(console.log)
onLCP(console.log)

我为 Core Web Vitals 回归设置了警报。当 P75 LCP 超过 2.5 秒时,团队会收到通知。这能在性能问题变成问题之前发现它们。

推荐工具

  • Lighthouse CI - CI/CD 中的自动化性能测试
  • WebPageTest - 详细性能分析
  • web-vitals - 真实用户监控
  • Chrome DevTools - 本地调试和性能分析
  • Cloudflare Analytics - 边缘真实用户指标

另见:TanStack Start 性能指南 和 Core Web Vitals 最佳实践。

改进来自系统地解决根本原因。渲染阻塞资源从 23 个减少到 3 个。字体加载不再导致布局偏移。图片渐进加载不阻塞渲染。

本文描述的技术现在是 TanStack Ship 默认配置的一部分。新项目开箱即用达到 Lighthouse 95+。无需额外工作就能获得性能优化。

性能优化是一个持续的过程。继续监控 Core Web Vitals 并在发布前解决回归问题。在 CI 管道中设置自动化 Lighthouse 检查,在性能回归到达生产环境前捕获它们。

性能投资的回报是用户满意度和 SEO。用户在快速网站上停留更长时间。搜索引擎优先排名快速网站。努力是值得的。

我还设置了真实用户监控来追踪生产中的 Core Web Vitals。Lighthouse 中的合成测试很有用,但真实用户数据揭示实际用户体验。我使用 web-vitals 库来捕获现场数据。

我为 Core Web Vitals 回归设置了警报。当 P75 LCP 超过 2.5 秒时,团队会收到通知。这能在性能问题变成问题之前发现它们。


常见问题

什么是好的 Lighthouse 评分?

好的 Lighthouse 评分是所有类别 90-100。具体性能方面:

  • 90-100:优秀(绿色)
  • 75-89:需要改进(橙色)
  • 0-74:较差(红色)

但是,评分应该是众多信号中的一个。真实用户体验(Core Web Vitals)比合成评分更重要。

我应该多久运行一次 Lighthouse?

在每次重大更改后运行 Lighthouse。对于生产站点,设置CI/CD 中自动化运行以在发布前捕获性能回归。对于监控,在生产环境上运行每周审计。

Lighthouse 和 Core Web Vitals 有什么区别?

Lighthouse 是在受控环境中模拟页面加载的合成测试工具。Core Web Vitals 是衡量现场真实用户体验的真实用户指标。两者都重要:Lighthouse 帮助你在开发过程中识别问题,而 Core Web Vitals 告诉你真实用户如何体验你的网站。

我能得到 100 分 Lighthouse 评分吗?

是的,获得 100 分 Lighthouse 评分是可能的,但不应该成为唯一目标。我见过 100 分评分的网站有糟糕的真实用户体验。专注于Core Web Vitals 阈值(LCP <2.5s、FID <100ms、CLS <0.1),而不是追求完美评分。

最大的性能收益是什么?

根据我的经验,消除渲染阻塞资源提供了最大的收益。将 20 个阻塞文件减少到 3 个,使性能评分提高了 37 分。第二大收益是图片优化,将 LCP 缩短了 3 秒。

我需要 CDN 吗?

对于大多数 SaaS 应用,是的,你需要 CDN。CDN 通过从靠近用户的边缘位置提供内容来减少延迟。Cloudflare Workers、Vercel 和 Netlify 都原生提供 CDN 分发。

如何优先处理性能工作?

使用影响 vs 难度矩阵:

  1. 高影响、低难度:从这里开始(图片优化、字体优化)
  2. 高影响、高难度:下一步做(代码分割、缓存架构)
  3. 低影响、低难度:填补空缺(压缩、混淆)
  4. 低影响、高难度:跳过或推迟

快速参考:性能检查清单

使用此清单审计你自己的应用:

  • 基准审计:运行 Lighthouse 并记录当前指标
  • 阻塞资源:识别并消除渲染阻塞 CSS/JS
  • 字体优化:实施 font-display: swap,预加载关键字体
  • 代码分割:按路由分割 JavaScript,延迟加载组件
  • 图片优化:使用 WebP/AVIF,实施响应式图片,懒加载首屏下方内容
  • 关键 CSS:提取并内联关键 CSS,其余异步加载
  • 缓存:配置 Cache-Control 头,使用 CDN 分发
  • 监控:设置自动化 Lighthouse CI 和真实用户监控
  • 文档:记录性能预算和优化决策