很长一段时间,我一直认为高 Lighthouse 分数主要是调优的结果。压缩图片、延迟脚本、修复布局偏移、调整主题、更换插件,每次出现新警告时就重复这个循环。
随着时间推移,这个假设与我在实践中看到的情况不再吻合。
持续获得高分的网站并非投入最多优化工作的网站。而是那些浏览器只需做较少工作的网站。
此时,Lighthouse 不再像是一个优化工具,而更像是架构选择的诊断信号。
Lighthouse 不评估框架或工具。它评估结果。
有意义的内容出现的速度有多快。
JavaScript 阻塞主线程的程度有多大。
布局在加载期间保持稳定的程度。
文档结构的可访问性和可爬取性如何。
这些结果是堆栈中更早做出的决策的下游效应。特别是,它们反映了在运行时有多少计算被推迟到浏览器。
当页面依赖大型客户端捆绑包才能使用时,低分并不令人意外。当页面主要是静态 HTML 且客户端逻辑有限时,性能就变得更加可预测。
在我运行的审计和参与的项目中,JavaScript 执行是 Lighthouse 回归的最常见来源。
这不是因为代码质量低。而是因为 JavaScript 在页面加载期间竞争单线程执行环境。
框架运行时、水合逻辑、依赖图和状态初始化都会在页面变为可交互之前消耗时间。即使是小型交互功能也常常需要不成比例的大型捆绑包。
默认假设使用 JavaScript 的架构需要持续努力来控制性能。将 JavaScript 视为明确选择加入的架构往往会产生更稳定的结果。
预渲染输出从性能方程中消除了几个变量。
请求时没有服务器端渲染成本。
内容出现不需要客户端引导。
浏览器接收可预测的完整 HTML。
从 Lighthouse 的角度来看,这改善了 TTFB、LCP 和 CLS 等指标,而无需进行针对性的优化工作。静态生成不能保证完美分数,但它显著缩小了失败模式的范围。
在重建我的个人博客之前,我探索了几种常见方法,包括默认依赖水合的基于 React 的设置。它们灵活且功能强大,但性能需要持续关注。每个新功能都会引发关于渲染模式、数据获取和捆绑包大小的问题。
出于好奇,我尝试了一种不同的方法,首先假设使用静态 HTML,并将 JavaScript 视为例外。我选择 Astro 进行这个实验,因为其默认约束与我想测试的问题一致。
突出的不是戏剧性的初始分数,而是随着时间推移维护性能所需的努力之少。发布新内容不会引入回归。小型交互元素不会级联到不相关的警告中。基准线就是更难被侵蚀。
我在处理这个获得完美 Lighthouse 分数的个人博客实验时,在单独的技术笔记中记录了构建过程和架构权衡。
这种方法并非普遍更好。
静态优先架构不适合高度动态、有状态的应用程序。它们可能会使严重依赖经过身份验证的用户数据、实时更新或复杂客户端状态管理的场景变得复杂。
假设客户端渲染的框架在这些情况下提供了更多灵活性,但代价是更高的运行时复杂性。重点不是一种方法更优越,而是权衡直接反映在 Lighthouse 指标中。
Lighthouse 呈现的不是努力,而是熵。
依赖运行时计算的系统随着功能的添加而积累复杂性。在构建时做更多工作的系统默认就限制了这种复杂性。
这种差异解释了为什么有些网站需要持续的性能工作,而其他网站只需最少的干预就能保持稳定。
高 Lighthouse 分数很少是激进优化过程的结果。它们通常自然地从最小化浏览器在首次加载时必须做的工作的架构中产生。
工具来来去去,但基本原则保持不变。当性能是默认约束而不是目标时,Lighthouse 就不再是你追逐的东西,而是你观察的东西。
这种转变与其说是选择正确的框架,不如说是选择允许复杂性存在的位置。


