模板碎片缓存不能只靠cache-control,因其常内联于主html中无独立url,浏览器不视为单独资源;仅当htmx等独立请求时,才可通过响应头单独控制缓存。

模板碎片缓存为什么不能只靠 Cache-Control
因为模板碎片通常内联在主 HTML 中,没有独立 URL,浏览器根本不把它当单独资源看待。你给 /index.html 设了 Cache-Control: public, max-age=3600,里面包含的 {% include "footer.html" %} 就跟着一起被缓存——哪怕 footer 内容已更新,用户刷新也看不到。
常见错误现象:
-
product_list.html碎片更新后,页面仍显示旧商品 - CDN 缓存了整页 HTML,导致新功能上线后用户端完全不可见
只有走独立 HTTP 请求(比如 HTMX 的 hx-get="/fragments/comments")时,碎片才拥有自己的响应头,此时才能单独设 Cache-Control: no-cache。否则,它只是字符串拼接结果,缓存控制权完全在主响应头上。
Django/Flask 中如何为 HTMX 碎片设正确响应头
HTMX 请求必须显式区分,不能依赖全局中间件或默认配置。Django 视图里直接 render() 返回碎片模板,若没手动设头,Nginx 或 CDN 很可能套上默认 public, max-age=3600,碎片就卡死不动了。
实操建议:
- 用
request.headers.get('HX-Request') == 'true'判断是否为 HTMX 请求(最可靠) - 碎片响应必须设
response['Cache-Control'] = 'no-cache, must-revalidate'或max-age=0 - 若内容依赖用户身份(如订单数),加
response['Vary'] = 'Cookie',避免不同用户看到彼此缓存 - 禁用
@cache_page装饰器——它默认设public, max-age=,和碎片场景冲突
缓存键必须包含可变维度,不能只用文件名
缓存键写成 "header" 是危险的。一旦模板含 {{ user.name }} 或 {{ locale }},多个用户就会拿到同一份 HTML,造成昵称错乱、语言混用等静默故障。
必须把所有影响输出的变量拼进 key:
- 当前 locale:
zh-CN/en-US - 设备类型:
mobile/desktop(影响<picture></picture>候选集) - AB 分组:
exp=checkout-v2 - 权限等级:
role=admin
示例:正确键是 "header:zh-CN:mobile:guest",而不是 "header"。Go 的 templ、Node.js 的 Marko 都不自动生成这个,得在调用层手动拼。
碎片缓存该用 Map 还是对象字面量
用 Map。对象字面量({})只能用字符串或 Symbol 作 key,而模板路径常含查询参数(如 header.html?theme=dark)或 Unicode 字符,强制 toString 后会归一化或乱码,导致 key 冲突。
Map 支持任意类型键(比如 fs.Stats、自定义类),还提供 size、clear()、有序迭代等能力,便于统计命中率或精准清理。Marko 编译器源码里就是用 new Map() 存编译结果,不是偶然。
例外情况:标签库路径(如 /node_modules/my-taglib/index.ttag)是标准化、无参、无编码的绝对路径,用 {} 安全——但这是少数。
真正容易被忽略的是 hydration ID 冲突:SSR 输出的碎片若含固定 id="modal-root",多个服务聚合时会覆盖;还有 CDN 对 /fragments/*.html 路径的默认缓存规则,往往比你代码里的 no-cache 更强势。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











