原文:https://blog.sentry.io/how-i-fixed-my-brutal-ttfb/?utm_medium=paid-community&utm_source=javascriptweekly&utm_campaign=fy25q1-jtbd-ttfb&utm_content=newsletter-jtbd-ttfb-learnmore 作者:Salma Alam-Naylor 译者:Gemini 1.5 PRO
绝招!我仅靠改善一个指标就提升了首页的核心网页生命力指标,你知道是什么指标吗?没错,正是首字节传输时间 (TTFB)!
通过两处微调数据抓取的方式,我成功地将 p75 TTFB
从令人抓狂的 3.46
秒降低到仅仅 704
毫秒。在这篇文章中,我将分享我是如何发现问题的,如何修复问题,以及在此过程中做出的重要决策。(别担心,我也会解释一下 “p75”
和 “TTFB”
的意思!)
Sentry
发现我网站的致命伤(网络慢)开发者们通常只会在短时间内关注性能:比如新网站上线时、新功能开发时、或者发现网站实在太慢时。2021-2022
年间,我为了提升网站性能而彻底重建了它,效果很棒。但是,随着时间的推移,我在网站的不同部分添加了许多额外的实验性技术,以至于性能再次变得糟糕透顶。过去几个月加载网站时我自己也注意到了这一点,但只有当我将 Sentry
性能监控添加到我的网站后,我才能够看到全貌。
使用 Sentry
等性能监控工具的优势在于,它可以跨所有操作系统、浏览器、移动设备、网络连接以及许多其他会影响用户体验的因素,向您展示网站的真实用户数据。以前,我曾经在开发过程中或网站构建期间使用过 Google Lighthouse 等工具来分析每个新版本的性能 - 但这仅仅给了我构建服务器在构建流水线中性能分数的快照。真实的用户数据才是更有价值的。
下面是我在 2024
年 2
月 14
日至 21
日期间未进行任何修改的主页性能表现。
对我来说,最迫切需要改进的是首字节传输时间 (TTFB)
。TTFB
是指浏览器向服务器发出请求后,接收到第一个响应字节所花费的时间。理论上,TTFB
越低,浏览器就越早开始渲染页面,用户就越早地在浏览器中看到内容,从而降低跳出率的可能性。
这里显示的 TTFB
值是第 75
个百分位数 (p75)
,这意味着 3.46
秒是在所有首页浏览次数中发现的最差分数,换句话说,有 25%
的用户等待页面加载的时间超过了 3.46
秒。这个糟糕的评分表明服务器在发送响应之前需要进行太多的处理。以下是当时的情况。
一段时间以来,我一直在请求时使用过两个独立的中间件函数(或边缘函数):一个用于从我的简报提供商那里获取最新订阅者数量,另一个用于从 Twitch API
获取我最新的流媒体视频或正在进行的当前直播流的最新缩略图。这两个函数都会在内存中获取初始的 HTTP
响应,从第三方 API
中获取一些数据,并相应地重写 HTML
代码。
这套架构的目的是为了最小化客户端数据获取,从而避免在显示静态生成的首页上的一些动态数据时阻塞主 JavaScript
线程(我讨厌骨架加载器)。从“向用户展示最新内容”的角度来看,这很棒,但缺点是它实际上重复了 HTTP
请求,因此将浏览器中显示内容的时间增加了一倍。除此之外,再加上来自世界任何地方(边缘)调用的两个独立第三方服务在静态区域的 API
延迟变化,情况就变得有些混乱了。
老实说,除了我之外,谁会关心准确的简报订阅者数量呢?我为什么要展示最新的随机生成的流媒体缩略图,尤其是大多数时候它都是一张我努力弄清楚如何编码的非常不友好的图片?人们不会坐在我的首页前每隔几分钟刷新一次页面 来获取更新的 Twitch
缩略图。我做得有点过火了。
此时,我的首要任务是改善 TTFB
。第一步很简单:删除获取简报订阅者数量的 Edge Function,而是改为在构建时获取数据并静态生成。除非我进行重建,否则该数字将不会是最新的,但是我们可以通过在数字后添加加号或在构建时 API
调用出错时返回一个字符串来解决轻微的不准确问题。
function getNewsletterSubscribers() {
let subscribers;
try {
const response = await fetch(
"https://api.email-provider.com/subscribers",
{
headers: {
//...
},
},
);
const result = await response.json();
subscribers = `${result.count.toString()}+`;
} catch () {
subscribers = "loads of";
}
return subscribers;
}
只移除中间件链中的一个 Edge
函数就显著改善了p75 TTFB
- 这个差异在用户加载页面时浏览器中**真真切切 (zhēn zhē qiē qiē)**感受到了。我监测了这一改动一周,TTFB
的 p75
值从 3.46
秒降低到仅 1.88
秒。这使 75%
的用户在浏览器上看到内容所需的时间减少了 46%
。通过一个小小的改动,所有核心网页生命力指标也得到了改善。
下一步是删除获取 Twitch
数据的 Edge
函数。我的假设是,即使数据还没有完全加载,将数据抓取移动到客户端并在数据准备好时将其写入 DOM
将会改善用户对页面性能的感知。由于中间件不再拦截 HTTP
请求,TTFB
将会降低,用户将更快地在浏览器中看到内容。
从服务器端将数据抓取移动到客户端的问题
下一步是删除获取 Twitch 数据的 Edge 函数。我的假设是,即使数据还没有完全加载,将数据抓取移动到客户端并在数据准备好时将其写入 DOM 将会改善用户对页面性能的感知。由于中间件不再拦截 HTTP 请求,TTFB 会降低,用户将更快地在浏览器中看到内容。
然而,这种方法也存在一些问题:
在改善网站性能,特别是核心网页生命力指标方面,总会存在权衡取舍。当你改善一个指标时,你最终可能会牺牲另一个指标的分数。在页面加载完成后抓取数据并更新 DOM 意味着在我的开发环境中,Twitch 流媒体缩略图的加载会延迟到一秒钟之后,从而导致页面内容发生位移。对于真实用户来说,这个延迟可能会更长。布局偏移通常发生在元素的大小在初始 HTML 或 CSS 中没有定义的情况下。为了解决这个问题有一些方法,例如包含一个大小相同的占位图片(在元素上指定高度和宽度),稍后由获取的图像替换它,但我认为这也不是一个好的体验,尤其是在网速较慢的情况下。
此时,我已经将一个性能问题从服务器端转移到了客户端,并创建了一个新的客户端性能问题。
现在是时候让我的网站尽可能地**静态化 (jìng tài huà)**了,但是这种方法仍然存在一些权衡取舍。
两年前我重建网站时,就刻意决定尽可能减少客户端 JavaScript 的使用,并使用静态站点来构建。网站简洁明了的设计也考虑到了这一点。我的网站一直被设计为我 Twitch 直播的营销渠道,因此我总是希望在首页包含一些关于 Twitch 的信息。当我于 2022 年首次启动网站重建时,我加入了一个指向下一个计划流的链接,该链接会在构建时被抓取并预生成。每次我在 Twitch 上上线或下线时,我都会使用 Webhook 重新构建网站以更新信息。如果您好奇的话,可以在 这里: link to website snapshot 查看网站刚上线时的快照。
为了在不引入新的 CLS 的情况下改善 TTFB,我再次将首页设置为静态的,并在每次我在 Twitch 上上线或下线时使用 Webhook(在我的 Twitch 机器人应用程序中)重新构建它。如果我不在 Twitch 上直播,则页面会在构建时使用我最新的流缩略图和信息静态生成。如果我正在 Twitch 上直播,则性能权衡就发挥作用了。
现在,我使用 Twitch 视频播放器嵌入代码来显示当前直播流,而不是在请求时从 Twitch API 获取最新直播流信息。这样做会额外的加载一些客户端 JavaScript 到页面中,这是它的缺点。但是,考虑到我每周直播时间只有大约六个小时,我认为这是一个可以接受的权衡。其余时间您将获得超快速的静态体验。
为了完整起见,这里简要展示了我首页 Twitch 组件的代码(该组件是一个构建静态 HTML 的 JavaScript 函数)。isLive 和 vodData 参数在构建时从 Twitch API 获取。
function TwitchInfo({ isLive, vodData }) {
return `
${
!isLive
? `<a href="${vodData.link}">
<p>${vodData.title}</p>
<p>${vodData.subtitle}</p>
<img
src="${vodData.thumbnail.url}"
alt="Auto-generated stream screenshot."
height="${vodData.thumbnail.height}"
width="${vodData.thumbnail.width}"
/>
</a>`
: `<div id="twitch-embed"></div>
<script src="https://embed.twitch.tv/embed/v1.js"></script>
<script>
new Twitch.Embed("twitch-embed", {/* options */});
</script>`
}
`;
}
最终,为了获得性能提升,您可能需要做出一些妥协。通过接受在每周几个小时内显示不准确的数据和加载一些额外的 JavaScript,我显著改善了首页的核心网页生命力指标,而首页也是我网站上访问量最大的页面。不准确的数据可能并不适用于每个网站和应用,但在权衡性能提升时可以考虑这一点。
在我移除首页上运行的两个 Edge 函数并完全恢复到静态构建之后,我将 p75 TTFB 降低了 80%,降至仅仅 704 毫秒。虽然仍有 25% 的用户体验到超过 704 毫秒的 TTFB,但我的 75% 用户可以在不到 704 毫秒的时间内看到加载完成的页面。到目前为止,我对取得的进展感到非常满意。如果这还不是静态网站以及静态网站生成器的完美宣传,那我不知道什么才是。
尽管如此,我仍然需要对首页做一些进一步的优化,例如一些本地图片优化(例如在支持的设备上提供 avif 和 webp 格式的图片)、静态呈现加载第三方 JavaScript 的 webring 组件、优化字体文件(因为我加载了一个非常花哨的字体文件,只使用其中三个字符作为背景纹理),并可能解决渲染阻塞的单个 CSS 文件。
正如我挚友 Cassidy Williams 所说:“你的网站开始很快,直到你添加了太多东西让它们变慢。”