Web 应用的浏览器缓存实用指南
Source: Dev.to
请提供您希望翻译的完整文本内容,我将为您翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。谢谢!
什么是浏览器缓存
- 浏览器缓存 – 本地于用户设备(重复访问的最快路径)。
- CDN / 代理缓存 – 位于靠近用户的边缘服务器的副本(降低源站负载和延迟)。
- Service‑worker 缓存(可选) – 应用控制的缓存逻辑,用于离线和高级更新策略。
Why caching matters
- Performance – 回访者会看到显著的速度提升。
- Cost – 更少的字节离开您的源站和 API。
- Reliability – 在流量高峰和故障期间,服务器负载更低。
主要挑战:部署后保持新鲜
金色模式(简单可靠)
- HTML 在每次导航时重新验证。
- 静态资源(JS、CSS、图片)拥有较长的缓存生命周期,但当内容更改时文件名会改变。
- API 使用 ETag 或 Last‑Modified 进行频繁重新验证。
如何实现它
1️⃣ 静态资源的内容哈希文件名
2️⃣ Cache‑Control 响应头
| 资源 | 响应头示例 | 原因 |
|---|---|---|
| HTML | Cache-Control: no-cache, must-revalidate (or max-age=0, must-revalidate) | 告诉浏览器和 CDN 在使用缓存副本前先向服务器确认。配合 ETag 或 Last‑Modified,检查成本低且快速。 |
| 哈希资源(JS/CSS/图片/字体) | Cache-Control: public, max-age=31536000, immutable | 因为 URL 与内容唯一对应,可长期缓存。 |
| API | Cache-Control: no-cache (or a short max-age with must-revalidate) + ETag or Last‑Modified | 当数据未变化时,客户端可快速收到 304 Not Modified 响应。 |
3️⃣ 部署顺序
- 首先上传新的哈希资源。
- 发布引用这些新资源文件名的更新后 HTML。
- (可选) 为 HTML 路径刷新 CDN 缓存,以便快速传播更新的入口页面。
何时使用缓存
- Production – 始终。它是基础的性能实践。
- Development – 保持缓存最小化以避免混淆(例如,在 DevTools 中禁用缓存或使用较短的
max-age)。 - Private or sensitive content – 对机密页面或数据使用更严格的标头,例如
Cache-Control: no-store。
什么是“no‑cache”真正的含义
它 不 意味着 “don’t store”;它意味着 “must revalidate with the origin before using a cached copy.”
部署后的示例用户流程
-
你部署了一个构建
main.js发生变化 → 变为main.<hash>.jsstyles.css未改变 → 仍为styles.<hash>.cssindex.html已更新,以引用新的文件名
-
用户访问时
- 浏览器重新验证
index.html并获取更新后的 HTML。 - 下载
main.<hash>.js(新 URL)。 - 复用已缓存的
styles.<hash>.css(相同 URL)。
- 浏览器重新验证
结果: 仅获取已更改的文件;未更改的文件瞬间加载。
配置示例(概念性)
Nginx
# HTML – always revalidate
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
}
# Static assets – long‑lived immutable cache
location ~* \.(js|css|png|jpg|jpeg|gif|svg|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
Apache (.htaccess)
# HTML
Header set Cache-Control "no-cache, must-revalidate"
# Static assets
Header set Cache-Control "public, max-age=31536000, immutable"
Node / Express
app.use(
express.static("dist", {
setHeaders: (res, path) => {
if (path.endsWith(".html")) {
res.setHeader("Cache-Control", "no-cache, must-revalidate");
} else {
res.setHeader(
"Cache-Control",
"public, max-age=31536000, immutable"
);
}
},
})
);
Framework tips
- React (Vite/CRA)、Angular、Vue CLI、Next.js、Nuxt – 生产构建通常会自动生成内容哈希的文件名。请确认
dist/build输出中包含哈希,并使用长期不变的 immutable 头部来提供这些资源。 - SSR 框架(Next.js、Nuxt) – 让框架自行管理资源哈希。确保 HTML 响应带有重新验证头部或使用带
must-revalidate的短 CDN TTL。对于动态页面,如可行,应返回ETag或Last‑Modified。 - 单页应用(SPA) – 始终重新验证
index.html。对于深层链接,使用相同的头部为应用路由提供index.html。
CDN 最佳实践
- 让你的源服务器发送上述头部;大多数 CDN 都会遵守它们。
- 部署后使 HTML 路由失效/清除,以便更新的入口点能够快速可见。
- 对于带哈希的资源,你通常不需要清除 JS/CSS,因为新构建会使用新文件名。
- 为 HTML 设置较短的 CDN TTL(例如 60–300 秒),以防止清除遗漏时作为安全网。
- 在 CDN(以及源服务器)上保留旧的带哈希资产一段时间,以避免仍在引用先前构建的用户出现 404,并支持回滚。
API 与数据新鲜度
- 使用
ETag或Last‑Modified与Cache-Control: no-cache(或短max-age+must-revalidate)。这可以避免陈旧数据,并通过 304 响应保持低带宽。 - 对于高度动态或敏感且绝不能重复使用的响应,请使用
Cache-Control: no-store。
Service workers(可选,高级)
-
Service workers 让你能够脚本化缓存和离线行为。
-
为缓存设定版本(例如
app-cache-v42),并在安装时预缓存资源,以实现可预测的离线行为。 -
每次部署时,发布一个新的 service worker。决定你的更新用户体验(UX):
- 当有可用更新时提示用户刷新(控制力强,清晰)。
- 使用
self.skipWaiting()和clients.claim()自动激活(更快,但需考虑 UX 权衡)。
-
绝不要让 service worker 永久提供陈旧的 HTML。 对 HTML 使用 network‑first 或 stale‑while‑revalidate 策略,以便及时发现更新。
如何强制重新验证
-
针对您自己: 硬刷新 (
Ctrl/Cmd+Shift+R) 或在 DevTools 中启用 “Disable cache”。 -
针对所有用户:
- 保持 HTML 为
no-cache, must-revalidate并返回ETag或Last‑Modified。 - 在部署后立即使 HTML 路由的 CDN 缓存失效。
- 在紧急情况下,临时在 HTML 上设置
Cache-Control: no-store以强制刷新,然后恢复。
- 保持 HTML 为
如何验证您的设置
浏览器开发者工具 → 网络
index.html在重新加载后应显示 200 或 304(而不是 “from cache”),表示已重新验证。- 带哈希的 JS/CSS 通常在部署之间会显示 “from disk cache” 或 “from memory cache”。
curl 检查
# Get headers (look for ETag)
curl -I https://your.site/index.html
# Conditional request – expect 304 if unchanged
curl -H "If-None-Match: <etag-value>" -I https://your.site/index.html
常见陷阱
- 跳过内容哈希 → 导致文件陈旧并且清除缓存变得复杂。始终使用带哈希的文件名进行缓存破坏(cache‑busting)。
- 对 HTML 设置过于激进的
max-age→ 会阻止更新及时到达用户。 - 部署后忘记清除 CDN 上的 HTML → 用户可能仍然收到旧的入口文件。
- 对可能在不更改文件名的情况下变化的资源错误地使用
immutable→ 浏览器将永远不会重新验证这些资源。
缓存破坏 & 长期缓存指南
查询字符串缓存破坏
file.js?v=123– 某些缓存会忽略查询参数。- 建议 使用内容哈希文件名(例如
file.1a2b3c.js)。
HTML 的长期缓存
- HTML 必须在每次请求时重新验证;否则用户将看不到新构建。
立即移除旧资产
- 使用旧 HTML 页面的用户仍可能请求之前哈希的文件。
- 保留 最近构建的资产一段安全时间窗口(例如 24 小时)。
Service‑worker 陷阱
- 永久提供陈旧 HTML 的 Service Worker 会导致更新失效。
- 确保 HTML 重新验证,并制定明确的更新策略(版本升级、提示、自动激活等)。
Security & Privacy Notes
- Never cache sensitive or private data. Use
Cache-Control: no-storefor such responses. - When using third‑party CDNs, plugins, or service‑worker libraries, verify they meet your organization’s security and compliance requirements.
- At Oracle: confirm alignment with internal guidelines before adopting external tools.
简单部署清单
-
构建
- 为 所有 静态资源输出内容哈希的文件名。
-
HTML 响应
Cache-Control: no-cache, must-revalidate- 包含
ETag或Last‑Modified。
-
静态资源
Cache-Control: public, max-age=31536000, immutable
-
发布顺序
- 先 上传新的哈希资源。
- 然后发布更新后的 HTML。
-
CDN
- 部署后使 HTML 路由的 CDN 缓存失效(推荐)。
-
回滚安全
- 保留最近几次构建的资源,以便回滚和为延迟返回的用户提供资源。
-
服务工作线程(如使用)
- 更新工作线程版本。
- 实现更新提示或自动激活策略。
常见问题
| Question | Answer |
|---|---|
| 用户是否始终获取最新的构建? | 会的。HTML 会重新验证并指向带有新哈希的资源;已更改的资源会重新下载,未更改的资源则从缓存中复用。 |
| 如果只有 JS 发生了变化怎么办? | HTML 会将 <script> 标签更新为新的哈希;重新验证会自动捕获该变化。 |
| 我需要让用户强制刷新吗? | 不需要——哈希加上正确的响应头会透明地处理更新。 |
| “no‑cache” 会很慢吗? | 不会。使用 ETag 或 Last‑Modified 时,浏览器通常会收到快速的 304 Not Modified 响应,并复用本地副本。 |
| 我可以跳过 CDN 清除吗? | 对于资源通常可以(它们已经带有哈希)。如需立即传播,可清除 HTML。 |
结束语
这种模式——重新验证的 HTML、带有长期不可变缓存的哈希资源以及带验证器的 API——在最小的运维开销下提供快速加载和安全更新。
- 从上述基础开始。
- 在浏览器的 Network 面板中验证行为。
- 随着应用的增长进行迭代。
如果您计划使用第三方工具或 CDN,请确认它们符合贵组织的安全和隐私标准。
在 Oracle: 在采用外部工具之前,请确认符合内部指南。