通过网络获取资源既速度缓慢又成本高昂:
- 大型响应需要在浏览器与服务器之间进行多次往返。
- 只有在所有关键资源全部下载完毕后,您的网页才会加载。
- 如果有人以有限的移动流量套餐访问您的网站,则每个不必要的网络请求都是在浪费他们的金钱。
如何避免不必要的网络请求?浏览器的 HTTP 缓存是您的第一道防线这未必是最强大或最灵活的方法,并且您对缓存响应的生命周期的控制力有限,但它非常有效,在所有浏览器中受支持,并且不需要太多工作。
本指南介绍了有效 HTTP 缓存实现的基础知识。
浏览器兼容性
实际上,并不存在一个名为 HTTP Cache 的 API。它是一系列网络平台 API 的通用名称。所有浏览器都支持以下 API:
HTTP 缓存的工作原理
浏览器发出的所有 HTTP 请求都会先路由到浏览器缓存,以检查是否存在可用于满足请求的有效缓存响应。如果匹配,则从缓存中读取响应,这样就消除了网络延迟和传输产生的数据费用。
HTTP 缓存的行为由请求标头和响应标头共同控制。理想情况下,您可以同时控制 Web 应用的代码(用于确定请求标头)和 Web 服务器的配置(确定响应标头)。
如需了解更深入的概念性概览,请参阅 MDN 的 HTTP 缓存文章。
请求标头:保留默认值(通常)
虽然 Web 应用的传出请求中应包含许多重要的标头,但浏览器在发出请求时几乎总是会代表您设置这些标头。影响新鲜度检查的请求标头(如 If-None-Match
和 If-Modified-Since
)会根据浏览器对 HTTP 缓存中当前值的理解而显示。
这是一个好消息,这意味着您可以继续在 HTML 中包含 <img
src="my-image.png">
等标记,浏览器会自动为您处理 HTTP 缓存,您无需进行额外的操作。
响应标头:配置您的网络服务器
HTTP 缓存设置中最重要的部分是网络服务器添加到每个传出响应的标头。以下标头都会影响有效的缓存行为:
Cache-Control
。服务器可以返回Cache-Control
指令来指定浏览器和其他中间缓存应如何缓存单个响应,以及缓存多长时间。ETag
。当浏览器发现缓存的过期响应时,可以向服务器发送一个小令牌(通常是文件内容的哈希值),以检查文件是否发生了更改。如果服务器返回相同的令牌,则文件是相同的,无需重新下载。Last-Modified
。此标头的用途与ETag
相同,但使用基于时间的策略来确定资源是否已更改,这与ETag
基于内容的策略不同。
有些网络服务器内置对默认设置这些标头的支持,而其他网络服务器则完全排除了标头(除非您明确配置它们)。关于如何配置标头的具体详情因您使用的 Web 服务器而异,您应该参阅相应服务器的文档以获取最准确的详细信息。
为节省搜索,以下是配置一些常用 Web 服务器的说明:
省略 Cache-Control
响应标头不会停用 HTTP 缓存!浏览器会有效猜测哪种类型的缓存行为最适合给定类型的内容。您可能想要获得比这种控制更多控制权,因此请花些时间配置响应标头。
您应使用哪些响应标头值?
在配置 Web 服务器的响应标头时,您应考虑两种重要场景。
版本化网址的长期缓存
假设您的服务器指示浏览器将 CSS 文件缓存 1 年 (Cache-Control: max-age=31536000
),但设计人员刚刚进行了紧急更新,您需要立即发布。您如何通知浏览器更新文件的“过时”缓存副本?您无法更改资源的网址,至少您不可更改。浏览器缓存响应后,缓存的版本将一直使用,直到其不再新鲜(由 max-age
或 expires
确定),或由于某种其他原因(例如,用户清除了浏览器缓存)从缓存中逐出为止。因此,在构建网页时,不同的用户可能最终使用的是文件的不同版本:刚刚提取了资源的用户将使用新版本,而缓存了早期(但仍有效)副本的用户将使用旧版本的响应。如何两全其美:客户端缓存和快速更新?您可以更改资源的网址,并在资源内容发生变化时强制用户下载新响应。通常,您可以通过在文件名中嵌入文件的指纹或版本号(例如 style.x234dff.css
)来实现此目的。
在响应针对包含“fingerprint”或版本控制信息的网址以及其内容绝对不会更改的网址请求时,请将 Cache-Control: max-age=31536000
添加到您的响应中。
设置此值可告知浏览器,当需要在接下来的一年内(31,536,000 秒,支持的最大值)随时加载同一网址,可以立即在 HTTP 缓存中使用该值,而无需向您的网络服务器发出网络请求。太棒了 - 您立即获得了避开网络所带来的可靠性和速度!
webpack 等构建工具可以自动执行为资源网址分配哈希指纹的流程。
针对没有版本控制的网址的服务器重新验证
遗憾的是,并非所有加载的网址都带有版本编号。也许您无法在部署 Web 应用之前包含构建步骤,因此不能向资源网址添加哈希。每个 Web 应用都需要 HTML 文件,这些文件(几乎!)永远都不会包含版本控制信息,因为如果需要记住要访问的网址为 https://example.com/index.34def12.html
,没有人会使用您的 Web 应用。那么,您可以对这些网址执行哪些操作?
在这种情形下,您需要承认失败。仅 HTTP 缓存的功能不足以完全避开网络。(别担心,您很快就会了解 Service Worker,它会为我们提供支持,扭转对您有利的局面。)但您可以采取几个步骤来确保尽可能快速高效地处理网络请求。
以下 Cache-Control
值可帮助您微调无版本控制网址的缓存位置和方式:
no-cache
。这会指示浏览器必须先每次向服务器重新验证,然后才能使用网址的缓存版本。no-store
。这会指示浏览器和其他中间缓存(例如 CDN)绝不存储该文件的任何版本。private
。浏览器可以缓存文件,但中间缓存不能。public
。响应可由任何缓存存储。
请参阅附录:Cache-Control
流程图,直观呈现确定要使用哪个 Cache-Control
值的过程。另请注意,Cache-Control
可以接受以英文逗号分隔的指令列表。请参阅附录:Cache-Control
示例。
除此之外,设置另外两个响应标头之一也会有所帮助:ETag
或 Last-Modified
。如响应标头中所述,ETag
和 Last-Modified
的用途相同:确定浏览器是否需要重新下载已过期的缓存文件。建议使用 ETag
,因为它更准确。
假设自首次提取以来经过了 120 秒,并且浏览器对同一资源发起了新请求。首先,浏览器会检查 HTTP 缓存并找到之前的响应。很遗憾,浏览器无法使用以前的响应,因为该响应现已过期。此时,浏览器可以发送新的请求并获取新的完整响应。但是,这样做效率低下,因为如果资源未发生变化,就没有理由下载缓存中已有的信息!这正是 ETag
标头中指定的验证令牌旨在解决的问题。服务器生成并返回任意令牌,该令牌通常是文件内容的哈希值或某种其他指纹。浏览器不需要知道指纹是如何生成的;只需在下一次请求时将指纹发送到服务器。如果指纹仍然相同,则表示资源未更改,因此浏览器可以跳过下载。
通过设置 ETag
或 Last-Modified
,您最终会提高重新验证请求的效率。它们最终会触发请求标头中提到的 If-Modified-Since
或 If-None-Match
请求标头。
当经过正确配置的 Web 服务器看到这些传入请求标头时,它可以确认浏览器 HTTP 缓存中已有的资源版本是否与 Web 服务器上的最新版本相匹配。如果匹配,则服务器可以使用 304 Not Modified
HTTP 响应进行响应,相当于“Hey, keep using what you've already get!”发送此类响应时要传输的数据非常少,因此,这通常比必须实际发回所请求资源的副本要快得多。

/file
并添加 If-None-Match
标头,以指示服务器仅在服务器上文件的 ETag
与浏览器的 If-None-Match
值不匹配时返回完整文件。在此示例中,这两个值确实匹配,因此服务器会返回 304 Not Modified
响应,并指示文件应缓存多长时间 (Cache-Control: max-age=120
)。
摘要
HTTP 缓存可以减少不必要的网络请求,是提高加载性能的有效方法。所有浏览器都支持该功能,而且设置起来不会费时费力。
不妨先从以下 Cache-Control
配置入手:
Cache-Control: no-cache
(适用于应在每次使用前通过服务器重新验证的资源)。Cache-Control: no-store
表示绝不应缓存的资源。Cache-Control: max-age=31536000
(适用于版本化资源)。
ETag
或 Last-Modified
标头可以帮助您更高效地重新验证过期的缓存资源。
了解详情
如果您希望深入了解使用 Cache-Control
标头的基础知识,请参阅 Jake Archibald 的缓存最佳实践和 max-age 陷阱指南。
如需了解如何针对回访者优化缓存使用情况,请参阅喜爱缓存。
附录:更多提示
如果您有更多时间,可通过以下方式优化 HTTP 缓存的使用:
- 使用一致的网址。如果您在不同网址上提供相同的内容,则这些内容将被多次提取和存储。
- 最大限度地减少用户流失。如果资源的一部分(例如 CSS 文件)经常更新,而文件的其余部分(例如库代码)却不频繁更新,请考虑将频繁更新的代码拆分到一个单独的文件中,并为频繁更新的代码使用短时长缓存策略,为不经常更改的代码使用长缓存时长策略。
- 如果您的
Cache-Control
政策在一定程度上可接受过时,请查看新的stale-while-revalidate
指令。
附录:Cache-Control
流程图
附录:Cache-Control
示例
Cache-Control 值 |
说明 |
---|---|
max-age=86400 |
响应可由浏览器和中间缓存缓存长达 1 天(60 秒 x 60 分钟 x 24 小时)。 |
private, max-age=600 |
浏览器(而不是中间缓存)可以将响应缓存长达 10 分钟(60 秒 x 10 分钟)。 |
public, max-age=31536000 |
响应可由任何缓存存储 1 年。 |
no-store |
响应不允许缓存,必须在每个请求中完整提取。 |