爱你的缓存 ❤️

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

本博文与 Love your cache 视频(该视频属于 2020 年 Chrome 开发者峰会的扩展内容部分)是配套的。请务必观看视频

当用户再次加载您的网站时,他们的浏览器将使用其 HTTP 缓存中的资源来帮助提高加载速度。但是,网络缓存标准可追溯到 1999 年,并且其定义非常广泛,因此确定是否可能再次从网络中提取文件(如 CSS 或图片)与从缓存中加载的文件有点不精确。

在这篇博文中,我将介绍一种合理且现代化的缓存默认配置,即实际上根本不执行缓存的默认设置。但这只是默认设置,当然,与“停用”相比,该设置更为精细。请继续阅读!

目标

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

  1. 确保您的用户获取的是可用的最新版本。如果您更改了某些内容,相应更改将很快生效
  2. 第 1 项操作,同时尽可能少地从网络中获取数据

大体上讲,您只需要在客户端再次加载您的网站时向其发送最细微的更改。此外,构建网站以确保以最高效的方式分发任何更改,也并非易事(详情请见下文和视频)。

话虽如此,在考虑缓存时,您也有其他需要考虑的因素,例如您已决定让用户的浏览器 HTTP 缓存长期保留您的网站,这样就无需网络请求即可处理该缓存。或者,您构建了一个 Service Worker,它在检查网站是否处于最新状态之前完全离线处理网站。这是一个极端选项,其有效且可用于许多类似离线应用的 Web 体验,但 Web 并不一定需要处于“仅缓存”极端,甚至不需要处于完全仅网络极端状态。

背景

作为 Web 开发者,我们对于有“过时缓存”的概念已经习以为常。但我们知道,有几种工具可以解决这个问题:执行“硬刷新”,打开无痕式窗口,或者结合使用浏览器的开发者工具来清除网站的数据。

普通用户在互联网上可能没有那么奢华。因此,虽然我们的一些核心目标是确保用户在第二次加载时获得愉快的体验,但确保用户不会遇到糟糕的时间或被卡住也非常重要。(如果您希望我谈论我们几乎是怎么导致 web.dev/live 网站卡住的,请观看视频!)

稍微背景知识,“过时缓存”的一个真正常见原因是缓存于 1999 年的默认选项。它依赖于 Last-Modified 标头:

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

您加载的每个文件都会额外保留其当前生命周期的 10%,正如浏览器所见。例如,如果 index.html 是在一个月前创建的,您的浏览器将再缓存三天。

这在当年只是出于善意,但考虑到当今网站的紧密集成本质,这种默认行为意味着可能会发生用户拥有为网站的不同版本设计的文件(例如,星期二发布版本中的 JS 和星期五发布版本中的 CSS)的状态,所有这些都是因为这些文件并不是在完全相同的时间进行更新。

光线充足的路径

现代的缓存默认设置是实际上不进行任何缓存,而是使用 CDN 使您的内容靠近用户。用户每次加载您的网站时,都会前往相应网络查看该网站是否为最新版本。此请求将由地理位置靠近每个最终用户的 CDN 提供,因此延迟时间较短。

您可以配置您的网站托管服务商,以响应使用以下标头的网络请求:

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

简单来说,该文件是无效的,您必须先通过网络对其进行验证,然后才能再次使用(否则,它只是“推荐文件”)。

就传输的字节数而言,此验证过程相对便宜 - 如果大型图片文件未更改,您的浏览器将收到小型 304 响应,但会导致延迟,因为用户仍必须转到网络才能找到答案。这是这种方法的主要缺点。它非常适合第一世界使用快速连接并且您选择的 CDN 覆盖范围非常广的用户,但不适用于移动网络连接速度较慢或基础架构不佳的用户。

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

"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,因为 CSS 会导致 HTML 的呈现方式发生变化
  • 在及时报道中使用大图片

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

    • 关于天气的 JSON 数据可能每小时才会发布一次,因此您可以将前一个结果缓存一小时,此结果在您的窗口中不会发生更改
    • 开源项目的构建可能有速率限制,因此请缓存构建状态映像,直到状态可能发生变化为止

摘要

当用户再次加载您的网站时,您就表示信心十足:他们希望能回访并获得更多您提供的产品或服务。就目前而言,这并不总是意味着缩短加载时间,并且您可以通过多个选项来确保浏览器只完成所需的工作,从而提供快速和最新体验。

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

另请参阅

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