爱你的缓存 ❤️

用户再次加载您的网站将使用其 HTTP 缓存,因此请确保其能够正常运行。

这篇文章与 Love your cache 视频相辅相成,该视频是 2020 年 Chrome 开发者峰会的扩展内容之一。请务必观看以下视频:

当用户第二次加载您的网站时,其浏览器会使用其 HTTP 缓存中的资源来加快加载速度。但是,网络缓存标准可追溯到 1999 年,并且定义非常宽泛。确定 CSS 或图片等文件是从网络重新提取还是从缓存加载,这并不是一门精确的科学。

在这篇博文中,我将介绍一种合理的新型缓存默认设置,即实际上根本不执行缓存的默认设置。但这只是默认设置,当然,它比仅仅“关闭”要细致得多。请继续阅读!

目标

当网站第 2 次加载时,您有两个目标:

  1. 确保您的用户获取的是最新版本 - 如果您更改了某些内容,这些更改应该能很快反映出来
  2. 执行第 1 步操作,同时尽可能少地从网络中提取数据

从广义上讲,您只希望在客户再次加载您的网站时,向其发送最小的更改。而且,要构建网站以确保以最有效的方式分发任何更改,这并非易事(下文和视频中会对此进行详细介绍)。

尽管如此,在考虑缓存时,您还可以使用其他控件。例如,您可以决定让用户的浏览器 HTTP 缓存长时间保留您的网站,这样就无需发出任何网络请求即可提供该网站。或者,您构造了一个 Service Worker,该 Service Worker 将完全离线处理某个网站,然后再检查该网站是否为最新版本。这是极端的选项,虽然有效,但适用于许多离线优先的类应用 Web 体验。不过,Web 不必完全依赖于缓存,甚至也不必完全依赖于网络。

背景

作为 Web 开发者,我们都习惯了“过时缓存”的概念。不过,我们几乎本能地知道可以通过以下方法来解决此问题:执行“强制刷新”、打开无痕式窗口,或使用浏览器的开发者工具来清除网站的数据。

互联网上的普通用户就没有这种奢侈的选择。因此,虽然我们的一些核心目标是确保用户在第二次加载时享受愉悦的体验,但确保用户不会遇到糟糕的时期,也不会陷入困境也非常重要。(如果您想听我讲述我们差点让 web.dev/live 网站卡住的情况,请观看视频!)

先了解一些背景信息,“缓存过时”的一个真正常见原因实际上是 1999 年以来的默认缓存。它依赖于 Last-Modified 标头:

显示不同资源由用户浏览器缓存多长时间的示意图
在不同时间生成的素材资源(灰色)将缓存不同的时间,因此第二次加载时可以同时获取缓存的素材资源和新素材资源

您加载的每个文件都会在其当前生命周期的基础上再延长 10% 的时间(以浏览器的视角来看)。例如,如果 index.html 是在一个月前创建的,那么您的浏览器将再将其缓存大约三天。

这在当时是出于善意的想法,但考虑到当今网站紧密集成的特性,这种默认行为意味着用户有可能会针对网站的不同版本(例如,星期二发布版本中的 JS 和周五发布版本中的 CSS)设计文件,这一切都因为这些文件并非在同一时间更新。

光线充足的小路

现代的缓存默认设置实际上是不进行任何缓存,并使用 CDN 将内容放置在靠近用户的位置。每当用户加载您的网站时,都会前往广告联盟查看广告是否是最新的。由于此请求将由地理位置靠近每个最终用户的 CDN 提供,因此延迟时间会很短。

您可以将 Web 主机配置为使用此标头响应 Web 请求:

Cache-Control: max-age=0,must-revalidate,public

这基本上表示,该文件完全无效,您必须先通过网络对其进行验证,然后才能再次使用该文件(否则,该文件只能作为“建议”使用)。

从传输字节数的角度来看,此验证过程相对较低 - 如果大型图片文件未发生更改,您的浏览器将收到一个小型 304 响应 - 但由于用户仍必须访问网络才能了解结果,因此会产生延迟时间。这是这种方法的主要缺点。这种方法非常适合第一世界国家/地区网速较快且您选择的 CDN 覆盖范围广的用户,但不适合移动网络连接速度较慢或基础架构较差的用户。

无论如何,这也是一种现代方法,是热门 CDN 上的默认方法 Netlify,但几乎可以在任何 CDN 上进行配置。对于 Firebase 托管,您可以在 firebase.json 文件的“hosting”部分中添加此标头:

"headers": [
 
// Be sure to put this last, to not override other headers
 
{
   
"source": "**",
   
"headers": [ {
     
"key": "Cache-Control",
     
"value": "max-age=0,must-revalidate,public"
   
}
 
}
]

因此,虽然我仍然建议将其作为合理的默认值,但它只是默认值! 请继续阅读,了解如何介入并升级默认值。

包含指纹的网址

通过在网站上提供的资源、图片等的名称中添加文件内容的哈希值,您可以确保这些文件始终包含唯一的内容,例如,这会导致文件名为 sitecode.af12de.js。当您的服务器响应对这些文件的请求时,您可以使用此标头配置最终用户的浏览器,以安全地指示它们将这些文件缓存很长时间:

Cache-Control: max-age=31536000,immutable

此值为一年(以秒为单位)。根据规范,这实际上等同于“永久”。

请务必不要手动生成这些哈希,因为手动操作太多了!您可以使用 Webpack、Rollup 等工具来帮助您解决此问题。请务必参阅工具报告,详细了解这些问题。

请注意,不仅 JavaScript 可以受益于带有指纹的网址;图标、CSS 和其他不可变数据文件等资源也可以以这种方式命名。(请务必观看上面的视频,详细了解代码拆分,该功能可让您在网站发生变化时减少发送的代码。)

无论您的网站采用何种缓存方式,这类带有指纹的文件对您可能构建的任何网站都非常有价值。大多数网站不会在每次发布时发生变化。

当然,我们无法以这种方式重命名面向用户的“友好”网页:将 index.html 文件重命名为 index.abcd12.html,这是不可行的,您不能要求用户每次加载您的网站时都访问新的网址!无法以这种方式重命名和缓存这些“易用”网址,这会导致我陷入一种可能的中间过程。

中间地带

在缓存方面,显然可以找到中庸之道。我提出了两个极端选项:永远不缓存永远缓存。您可能希望将一些文件缓存一段时间,例如我上面提到的“友好”网址。

如果您确实希望缓存这些“友好”网址及其 HTML,不妨考虑它们包含哪些依赖项、如何缓存它们,以及缓存它们的网址一段时间可能会对您产生哪些影响。我们来看看包含以下图片的 HTML 网页:

<img src="/images/foo.jpeg" loading="lazy" />

如果您通过删除或更改这种延迟加载的图片来更新或更改您的网站,则查看 HTML 缓存版本的用户可能会看到不正确或缺失的图片,因为他们在重新访问您的网站时仍缓存了原始 /images/foo.jpeg

如果你要谨慎,这可能不会影响你。但总的来说,请务必注意,当您的网站被最终用户缓存后,就不再仅存在于您的服务器上。而是可能以碎片的形式存在于最终用户浏览器的缓存中。

一般来说,大多数有关缓存的指南都会介绍这类设置,也就是说,您想缓存一小时还是几小时,等等。要设置此类缓存,请使用如下标头(缓存 3600 秒,即 1 小时):

Cache-Control: max-age=3600,immutable,public

最后一点。如果您要创建的及时内容通常只会被用户访问一次(例如新闻报道!),我认为这些内容绝不应缓存,而应使用上述合理的默认设置。我认为,当用户希望始终看到最新、最精彩的内容(例如新闻报道或时事的关键更新)时,我们常常高估缓存的价值。

非 HTML 选项

除了 HTML 之外,适用于介于两者之间的文件的其他选项包括:

  • 一般情况下,寻找不会影响其他素材资源的素材资源

    • 例如:避免使用 CSS,因为它会导致 HTML 呈现方式发生变化
  • 在及时报道中使用的大图片

    • 用户可能不会多次访问任何一篇文章,因此请勿永久缓存照片或主打图片,以免浪费存储空间
  • 表示本身具有生命周期的资源

    • 天气相关的 JSON 数据可能每小时只会发布一次,因此您可以将上一个结果缓存一小时,而这些数据不会在窗口中发生变化。
    • 开源项目的 build 可能会受到速率限制,因此请缓存 build 状态图片,直到状态可能发生变化

摘要

当用户第二次加载您的网站时,您已经获得了他们的信任,他们希望回访您的网站,进一步了解您提供的产品和服务。此时,不仅仅是缩短加载时间,您还可以使用多种方法来确保浏览器仅执行必要的工作,从而提供快速且最新的体验。

缓存在 Web 上并不是一个新概念,但可能需要一个合理的默认设置。不妨考虑使用默认设置,并在需要时强制选择更好的缓存策略。感谢您阅读本邮件!

另请参阅

有关 HTTP 缓存的一般指南,请参阅使用 HTTP 缓存防止不必要的网络请求