实现完美 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
