爱你的缓存 ❤️

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

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

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

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

目标

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

  1. 确保用户获得最新版本。如果您更改了某些内容,这些更改应快速反映
  2. 执行第 1 步,同时尽可能少从网络提取内容

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

话虽如此,在考虑缓存时,您也有其他选择。或许您已经决定让用户的浏览器 HTTP 缓存在您的网站上保留很长时间,以至于无需任何网络请求来处理它。或者,您构建了一个 Service Worker,它会先完全离线提供网站,然后再检查网站是否是最新的。这是极端的选项,虽然有效,但适用于许多离线优先的类应用 Web 体验。不过,Web 不必完全依赖于缓存,甚至也不必完全依赖于网络。

背景

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

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

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

显示用户浏览器缓存不同资源的时长的图表
在不同时间生成的资源(以灰色显示)将缓存不同的时间,因此第 2 次加载可以获得缓存资源和全新资源的组合

您加载的每个文件都会在其当前生命周期的基础上再延长 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 缓存避免不必要的网络请求