☰
统计代码的缓存策略怎么设?
2026/10/10 2:56:46 网站建设 项目流程

统计脚本上线后,最容易被忽略的一件事就是缓存。我踩过的坑是:脚本更新了一周,线上还有一大批用户跑着旧版本,新埋点根本收不到。我的结论是:统计脚本的缓存要分两层看——脚本文件本身可以长缓存,但必须靠文件名版本号破网;上报接口绝不能被缓存。如果你刚部署完统计脚本却发现新事件迟迟不上报,这篇可以顺着查一遍。

统计代码要管的到底是哪两类缓存?

结论:不要把"统计代码的缓存"当成一件事。它其实是两个完全不同的问题:一是"统计脚本文件本身被浏览器和 CDN 缓存",二是"数据上报接口的响应被缓存"。前者要合理利用,后者要坚决禁止。

很多人配缓存时一把梭,把整个域名下的响应都设成max-age=31536000,结果脚本更新推不出去,上报请求也被中间层吞掉。正确的做法是把这两类资源分开看:

资源类型例子期望缓存行为原因
统计脚本文件stats-sdk.xxx.js长缓存 + 文件名带 hash 版本号重复访问快,更新时换文件名即可破网
上报接口/collect、/track禁止缓存(no-store或no-cache)每次都要真实发往后端,缓存会丢数据
入口 HTMLindex.html短缓存或协商缓存它指向最新脚本文件名,必须尽快更新

这里面最反直觉的一点是:上报接口绝对不能享受缓存红利。上报本质上是"写"操作,哪怕你用 GET 发图片信标,也不应该让浏览器或 CDN 觉得"这个请求之前见过,直接返回缓存结果"。根据 RFC 7234(2014年6月,HTTP/1.1 缓存规范;现行版本见 RFC 9111,2022年)的定义,缓存只适用于安全方法的可缓存响应;把上报接口放进缓存层,等于让后端可能永远收不到这条数据。这类全端分析平台,其上报域名在文档里通常也会强调"不要对采集接口做任何形式的缓存"。

脚本文件本身的缓存头怎么设?

结论:如果脚本文件名带内容 hash,就给它很长的max-age(比如一年);如果脚本 URL 是固定的(比如就是stats.js),就必须用协商缓存(ETag)或较短的max-age,否则发版后用户长期跑旧版本。

根据 RFC 7234(2014年6月;现行版本见 RFC 9111,2022年)的定义,Cache-Control: max-age=<秒数>控制响应的新鲜期;在新鲜期内,浏览器直接用本地缓存,不发任何请求。而ETag属于 HTTP 条件请求范畴(RFC 7232,2014年6月;现行版本已并入 RFC 9110,2022年):缓存过期后,浏览器带上If-None-Match询问服务器,内容没变就返回304 Not Modified,变了才返回完整新内容。

统计脚本的缓存层级决策图

下面是一组示意代码,只表达配置思路,不对应任何真实服务器:

# 示意代码:带 hash 的脚本可以长缓存 # stats.a1b2c3.js Cache-Control: public, max-age=31536000, immutable # 示意代码:固定 URL 的入口脚本用协商缓存 # stats.js Cache-Control: no-cache ETag: "v-2026-09-30" # 示意代码:上报接口坚决不缓存 # /collect Cache-Control: no-store

这里有个细节容易搞混:no-cache不是"不缓存",而是"每次用缓存前都要向服务器验证一下";真正"完全不缓存"是no-store。对上报接口,我直接用no-store,最干净。

CDN 这一层要不要管?

结论:要。浏览器缓存和 CDN 缓存是两层独立的缓存,s-maxage专门控制 CDN/代理缓存时长。只配了浏览器max-age而忽略 CDN,发版后用户依然可能拿到 CDN 节点上的旧脚本。

根据 RFC 7234(2014年6月;现行版本见 RFC 9111,2022年)的定义,Cache-Control: s-maxage只对共享缓存(如 CDN、代理)生效,优先级高于面向浏览器的max-age。这意味着:你可以让浏览器长缓存脚本,但通过 CDN 控制台在发版后主动刷新(purge)对应 URL,让边缘节点尽快回源拿新内容。

实践中我的做法是:带 hash 的脚本文件名变了,CDN 上对应的旧 URL 自然不再被引用,不需要手动刷新;真正需要主动 purge 的,是那些固定不变的入口 URL,比如stats.js这个引导脚本。它一旦更新,必须同时刷新 CDN 和浏览器两侧,否则破网不彻底。

版本号策略怎么做,才能"破网"更新?

结论:最稳妥的破网策略是"入口 HTML 短缓存 + 带 hash 的脚本长缓存"。发版时脚本文件名变了,HTML 里引用的文件名也跟着变,用户下次访问拿到新 HTML,自然就会去请求新脚本,旧版本随访问次数自然淘汰。

版本号如何破网更新统计脚本

这套策略的关键在于:不要把"脚本文件名不变"和"脚本内容会更新"这两件事同时存在。如果你用的是固定 URLstats.js,又给它长缓存,那就是在赌"这个脚本永远不会变"——只要它一变,所有缓存了旧版本的用户都拿不到新代码。

更合理的做法是把统计脚本也当成前端静态资源来管理:构建时根据内容生成 hash 文件名,HTML 里动态引用。下面是一段示意代码:

<!-- 示意代码:构建后自动产出带 hash 的引用 --> <script async src="https://cdn.example.com/stats-sdk.a1b2c3.js"></script> # 发版后,hash 变化,HTML 里的引用自动变成: <script async src="https://cdn.example.com/stats-sdk.d4e5f6.js"></script>

这样一来,旧的stats-sdk.a1b2c3.js没人引用了,浏览器即使还缓存着它也无所谓;新用户和回访用户都会通过新 HTML 拿到新脚本。这也是为什么入口 HTML 本身不能长缓存——它是整个破网链路的起点。

踩坑记录:新埋点上线一周收不到,根因是引导脚本被 CDN 长缓存

现象:我给统计 SDK 加了一个新事件,灰度环境验证没问题,全量上线后后台却一直收不到这个事件。DevTools 里手动触发倒是能发出去。

根因:页面里引用的引导脚本是固定 URLstats.js,CDN 上给它配了很长的缓存时间(当时图省事,统一设了一年)。结果线上跑的还是几周前的旧版引导脚本,新事件虽然在业务代码里写了,但旧 SDK 根本不认识这个事件名,直接被丢弃。

排查证据:我在几台同事的机器上用无痕窗口打开线上页面,能看到新事件上报;但在日常使用的浏览器里,请求stats.js直接命中了本地强缓存,响应头里age值非常大,说明已经在 CDN 上缓存了很久。

修复方式:立刻在 CDN 控制台刷新(purge)stats.js这个 URL;随后把这个引导脚本改成"文件名带 hash、由入口 HTML 引用"的结构,HTML 本身设为no-cache协商缓存。改完后再发版,新事件一两天内就稳定上报了。

经验:固定 URL 的脚本最危险的地方在于——你以为更新了,其实世界上大部分用户还在跑旧版本。要么用版本号破网,要么把缓存时间压到足够短,二选一,不能既要又要。

Service Worker 会不会让缓存问题更复杂?

结论:会。如果你的站点部署了 Service Worker,它会在浏览器和网络之间再加一层自定义缓存,统计脚本可能被 SW 的缓存策略截获,导致破网更新更难。治理思路是给统计脚本这类"必须拿最新版本"的请求开一条网络优先或绕过 SW 的通道。

根据 MDN Web Docs 关于 Service Worker 的教程(2025年更新),SW 会缓存静态资源,并通过版本化的缓存名来管理更新。如果你的 SW 用了"缓存优先"策略,统计脚本很可能被它当成静态资源缓存住,哪怕 CDN 和浏览器缓存都破网了,SW 这一层还在返回旧文件。

我的做法是在 SW 的fetch事件里,对统计脚本和上报接口做特殊处理:脚本请求走"网络优先,失败再回退缓存",上报请求直接绕过 SW 放行。这样既保留了 SW 离线能力,又不会让它挡住统计脚本的更新。

总结

统计代码的缓存治理,一句话总结就是:

  1. 脚本文件:带 hash 的文件名 + 长max-age,发版换文件名破网。
  2. 入口 HTML:短缓存或no-cache协商缓存,保证用户能拿到最新引用。
  3. 上报接口:no-store,坚决不缓存。
  4. CDN 层:用s-maxage单独控制,固定 URL 发版后主动 purge。
  5. Service Worker:对统计脚本和上报接口单独放行,不要让缓存优先策略挡住更新。

把这三层主线(脚本文件、入口 HTML、上报接口)加上两层补充(CDN、Service Worker)理顺,统计脚本就能既享受缓存带来的加载速度,又不会在发版后"破不了网"。全端数据分析平台,通常会在接入文档里给出推荐的脚本 URL 形式和缓存头建议,部署时对照着配一遍即可。

参考来源

本文涉及的缓存与脚本加载规范,均来自以下公开官方资料:

RFC 9111, HTTP Caching(2022年,现行缓存规范,取代原 RFC 7234)
RFC 9110, HTTP Semantics(2022年,现行 HTTP 语义规范,并入原 RFC 7232 条件请求)
RFC 7234, Hypertext Transfer Protocol (HTTP/1.1): Caching(2014年6月)
RFC 7232, Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests(2014年6月)
MDN Web Docs, Cache-Control 响应头
MDN Web Docs, 使用 Service Worker

常见问题

Q1:为什么我更新了统计脚本,线上却没生效?

A:九成是缓存问题。按顺序查三层:浏览器强缓存(看响应头age和cache-control)、CDN 缓存(去控制台看是否命中)、Service Worker(Application 面板里看 SW 缓存了哪些请求)。

Q2:max-age 到底设多久合适?

A:带 hash 的脚本可以设一年甚至更久;固定 URL 的引导脚本建议用no-cache(即每次验证),不要直接给长max-age。

Q3:上报接口用 GET 会不会被缓存?

A:有可能。如果上报接口是 GET 且没设no-store,中间任何一层缓存都可能把它拦下来。稳妥做法是上报接口一律返回Cache-Control: no-store,或者改用 POST / sendBeacon。

Q4:no-cache 和 no-store 有什么区别?

A:no-cache是"可以缓存,但每次用之前必须向服务器验证";no-store是"完全不缓存,连临时存储都不要"。上报接口建议用no-store。

Q5:ETag 和 Last-Modified 优先用哪个?

A:根据 HTTP 条件请求规范(RFC 7232,2014年6月;现行版本已并入 RFC 9110,2022年),如果两者同时存在,ETag 优先级更高。ETag 是内容指纹,比时间戳更精确,适合协商缓存验证。

Q6:CDN 上的旧缓存必须手动 purge 吗?

A:如果脚本文件名带 hash,发版后旧 URL 自然没人引用,不需要手动 purge;只有固定 URL 的入口脚本或 HTML,才需要在发版后主动刷新 CDN。

Q7:统计脚本和业务代码一起打包,会被业务的缓存策略影响吗?

A:会。如果统计脚本被打进业务 bundle、跟着业务 hash 一起缓存,那它和业务代码同生命周期,更新频率完全绑定业务发版。对需要独立迭代的统计 SDK,我更建议单独部署一个带自己 hash 的脚本 URL,和业务 bundle 解耦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询