实现完美 Lighthouse 分数背后的每一项优化

我一直在我的网站上运行 Lighthouse。在每次本地运行和 Vercel 的 Real Experience Score 中,它都保持在 100 分。

这些分数并非来自通用的检查清单。我是通过将工作从浏览器转移到构建阶段来实现它们的。

以下是我的具体做法。

将工作转移到构建时

大多数指南会告诉你对所有内容进行懒加载。我更倾向于预渲染。我使用启用了预渲染功能的 TanStack Start。

这在构建期间将整个网站转换为静态 HTML。浏览器不必为了显示第一个页面而执行繁重的 JavaScript。当用户按下回车键时,HTML 已经准备就绪了。

预计算复杂逻辑

我的首页有一个带有 5,000 个点的世界地图。通常,一个库会在主线程上解析 GeoJSON 并进行数学计算。这会阻塞页面 1,000 毫秒。

我通过将数学计算转移到构建脚本中解决了这个问题。

  • 我为所有 5,000 个点生成了一个单一的 SVG path 字符串。
  • 对于浏览器来说,渲染一个 path 比渲染 5,000 个独立的 circle 要快得多。
  • 我预先计算了坐标查找表,这样浏览器在运行时就无需进行任何数学计算。

1,000 毫秒的延迟变成了一次瞬间的绘制。

优化字体加载

我对主要字体使用 rel="preload"

一个常见的错误是忘记了 crossOrigin 属性。如果你省略它,浏览器会获取两次字体。这会破坏你的 Largest Contentful Paint (LCP)。我只预加载首屏(above the fold)使用的三种字体。

使用正确的动画工具

我在地图标记上使用 SMIL 进行简单的脉冲动画。这比使用 React state 来驱动动画循环的开销更低。它让浏览器在合成线程(compositor thread)上处理工作。

对于复杂的路径,我使用 motion。我保持简单。我在挂载(mount)时进行一次动画,并避免监听滚动位置。

坚持使用矢量图和 WebP

如果是 Logo 或形状,请使用 SVG。如果是照片,请使用 WebP。这可以保持较小的文件体积并防止布局偏移(layout shifts)。

避免过度工程

我不使用图像 CDN。我不使用复杂的代码分割(code-splitting)。我的网站很小,所以路由级分割就足够了。

完美的分数可能只是一个虚荣指标。真正的目标是衡量你的性能,并将尽可能多的工作从用户的设备中转移出去。

我的作品集:brodin.dev

源代码:github.com/NathanBrodin/Portfolio

TanStack Start 预渲染:tanstack.com/start

Paper Shaders:shaders.paper.design

完整文章:https://dev.to/nathan-brodin/every-optimization-behind-a-perfect-lighthouse-score-283n