页面缓存、CDN 与同意横幅:如何避免提供错误的同意状态
页面很快,同意却是错的
页面缓存是提升速度、降低托管成本的最佳做法之一,同时也是最容易在不知不觉中破坏同意设置的方式之一。缓存只会严格按照指令行事:它把响应存储一次,并提供给所有匹配同一个键的人。如果这个被存储的响应包含任何专属于某位访客同意选择或所在位置的内容,下一位访客也会收到它。
本指南涵盖通常位于发布商网站前面的各个缓存层 — CDN 边缘缓存、整页缓存插件、反向代理、service worker 以及浏览器本身 — 以及如何让每一层都不提供错误的同意体验。
核心规则:不要把同意放进被缓存的 HTML
最安全的架构是把被缓存的 HTML 页面渲染为同意中立的。页面包含 CMP 加载器和处于阻止或等待状态的标签,但不包含关于这位特定访客的任何决定。随后 CMP 在浏览器中运行,读取已存储的选择,决定是否显示横幅,并相应地释放标签。
当服务器在渲染时做出同意决定,问题就开始了,例如:
- 主题或插件仅在存在同意 Cookie 时才输出广告或分析标签;
- 服务器端代码对已经做出选择的访客省略横幅标记;
- 在内联脚本变量中嵌入访客同意状态的模板。
一旦这样的页面被缓存,第一位访客的状态就会变成所有人的状态。拒绝了跟踪的访客可能收到标签已启用的页面,或者新访客可能永远看不到横幅,因为缓存副本来自一位已经关闭了横幅的人。
为什么“Vary: Cookie”很少是答案
针对按访客而异的内容,教科书式的修复方法是告诉缓存根据 cookie 头进行区分。实际上,访客携带许多具有唯一值的 Cookie,因此按整个 cookie 头区分通常会使缓存命中率降到近乎为零。一些 CDN 允许你改用单个 Cookie 的值来构建缓存键,如果你的同意 Cookie 可能的状态数量很少,这是可行的,但它会增加复杂性,并且仍会使缓存副本的数量成倍增加。把决定移到浏览器中更简单,也能让缓存保持高效。
边缘的地理位置
许多发布商按地区显示不同的横幅:欧盟和英国使用选择加入横幅,美国各州使用退出通知,其他地方则使用更轻量的形式。如果服务器根据访客的 IP 地址选择变体,而该页面被缓存,欧洲读者就可能收到缓存的美国页面,反之亦然。
有三种模式可以避免这种情况:
- 客户端地理定位。让 CMP 在浏览器中确定地区,使被缓存的 HTML 对所有人保持一致。
- 把国家加入缓存键。如果必须在边缘做决定,就把 CDN 提供的国家代码加入缓存键。每个国家有一份缓存副本,行为可预测。
- 不缓存的边缘逻辑。在边缘函数中运行地区决策,将一个很小的、不缓存的值注入到原本被缓存的页面中。
无论选择哪一种,都要将其记录下来。地理位置缺陷是最难重现的问题之一,因为只有当某地的访客恰好收到了别处某人缓存的页面时才会出现。
缓存 CMP 脚本本身
长期缓存 CMP 加载器对性能有利,但在配置变更时会付出代价。如果你更新了供应商、目的或横幅文字,而脚本或配置文件被缓存了一年,一些访客将持续看到旧版本。请检查你的 CMP 如何为文件设定版本:文件名或版本参数的变化,让你既能激进地缓存,又能立即推出变更。另外,如果 CMP 依赖查询字符串,请确认你自己的 CDN 不会从第三方脚本 URL 中去掉查询字符串。
Service Worker 与离线缓存
带有渐进式 Web 应用层的网站经常在 service worker 中缓存 HTML。同样的规则适用:绝不要存储反映了特定同意决定的页面。将同意相关的端点和偏好中心页面排除在 service worker 缓存之外,并确保始终能够获取全新的 CMP 配置。
后退/前进缓存
现代浏览器会把最近访问的页面保留在内存中,以便后退按钮能即时恢复它们。如果访客在某个页面上打开偏好中心,撤回了同意,然后按下后退,被恢复的页面可能仍有来自之前的标签在运行。请监听 pageshow 事件并检查其 persisted 属性:当页面是从该缓存恢复时,重新读取已存储的同意状态,并停止或启动标签使其保持一致。
测试流程
- 在干净的会话中加载一个页面,拒绝,然后加载第二个页面,确认没有任何跟踪标签触发。
- 用第二个干净的浏览器重复一遍,确认横幅会出现,以证明它没有被缓存掉。
- 检查响应头,查看页面是否来自缓存,并比较有无同意 Cookie 时的 HTML — 它们应该完全相同。
- 清除缓存后至少从两个地区测试,待缓存预热后再测试一次。
- 撤回同意,使用后退按钮,并验证标签是否停止。
核心要点
- 被缓存的 HTML 应当是同意中立的;让 CMP 在浏览器中做决定。
- 避免按完整 cookie 头区分;这会摧毁命中率。
- 如果横幅因地区而异,请在客户端判断,或把国家加入缓存键。
- 为 CMP 文件设定版本,使较长的缓存时间不会延迟配置变更。
- 处理好 service worker 和后退/前进缓存,并在缓存预热的状态下测试,而不只是在清除缓存之后。