模板碎片缓存不能只靠浏览器Cache-Control,因为碎片通常内联于主HTML中,继承其缓存策略;只有作为独立HTMX请求(如/hx/fragment)时才需单独配置no-cache等响应头,并须同步管控CDN路径规则。

模板碎片缓存为什么不能只靠浏览器 Cache-Control
服务端渲染的模板碎片(如 Django 的 fragments/user_info.html、Jinja2 的 {% include %} 片段)本质上是 HTML 字符串,由后端动态拼装并返回。浏览器对它的缓存行为完全取决于响应头——但问题在于:你根本没法给每个碎片单独设 Cache-Control,除非它们走独立 HTTP 请求(比如 HTMX 的 hx-get)。多数情况下,碎片只是主页面的一部分,随 index.html 一起下发,此时浏览器只认整个 HTML 的缓存策略,碎片本身无独立缓存身份。
常见错误现象:product_list.html 碎片内容更新了,但用户刷新后仍看到旧商品列表;user_dashboard.html 已加新功能,线上却始终不生效。
- 碎片被内联进主模板时,它继承主 HTML 的缓存头——若主页设了
no-cache,碎片也跟着每次校验;若主页被 CDN 强缓存,碎片就彻底锁死 - HTMX 场景下,碎片走单独请求(如
GET /fragments/comments),这时才需要单独配置缓存头,且必须和主 HTML 分开管理 - 服务端模板引擎(如 Django/Jinja2)自身不提供碎片级缓存控制,全靠你手动在视图里判断请求头(如
HX-Request)并设置响应头
Django 视图中如何为 HTMX 碎片设正确缓存头
当 Django 视图同时支持完整页加载和 HTMX 局部请求时,必须区分响应类型,并为碎片响应设置短缓存或协商缓存。否则碎片可能被浏览器或 CDN 长期缓存,导致局部更新失效。
典型错误写法:return render(request, 'fragments/comment_section.html', context) 没设任何缓存头,Nginx 默认可能加 Cache-Control: public, max-age=3600,碎片就卡住不动了。
立即学习“前端免费学习笔记(深入)”;
- 检测 HTMX 请求:
if request.headers.get('HX-Request') == 'true',这是最可靠标识 - 碎片响应必须设
Cache-Control: no-cache, must-revalidate或max-age=0,禁止强缓存 - 若碎片内容高度动态(如实时评论数),可进一步加
Vary: X-Requested-With或Vary: Cookie,避免不同用户看到彼此缓存 - 不要用
cache_page装饰器直接套碎片视图——它默认设public, max-age=...,和碎片场景冲突
Jinja2 + Flask 场景下碎片缓存的陷阱
Jinja2 本身不处理 HTTP 缓存,但 Flask 的 render_template 返回的是 Response 对象,容易忽略手动设置响应头。尤其当使用 {% include %} 内联碎片时,开发者常误以为“没走单独请求就不用管缓存”,结果碎片随主页面被 CDN 缓存一整天。
真实风险点:Flask 默认不设 Cache-Control,某些 WSGI 服务器(如 Gunicorn + Nginx)会补默认值,而这个默认值往往对碎片极不友好。
- 内联碎片无需单独响应头,但主模板的响应头必须严格设为
no-cache,否则碎片随主页一起被锁 - 若用
render_template_string动态生成碎片并返回 JSON/HTML 混合响应,必须显式调用response.headers['Cache-Control'] = 'no-cache' - 避免在模板里用
{% set fragment_html = render_template(...) %}这类预渲染——它绕过响应生命周期,缓存头完全失控 - Nginx 配置中,对匹配
/fragments/.*的路径要单独加add_header Cache-Control "no-cache";,防止 upstream 漏配
CDN 对碎片路径的缓存穿透怎么破
哪怕服务端响应头正确,CDN(如 Cloudflare、阿里云 DCDN)也可能无视它,尤其当你没在 CDN 后台显式配置路径缓存规则时。典型表现:本地 curl -I 看到 Cache-Control: no-cache,但用户端 Network 面板里碎片请求状态码却是 200 (from cache)。
这不是浏览器问题,是 CDN 在中间层做了缓存覆盖。它比浏览器缓存更难排查,因为请求根本没到你的服务器。
- 必须登录 CDN 控制台,找到对应域名的 Page Rule 或缓存策略,确认
/fragments/*或/api/fragment/*这类路径是否被归入“静态资源”类并设了长缓存 - 临时验证方法:在响应头里加一个自定义头
X-Fragment-Cache: bypass,然后在 CDN 规则里设置“若存在该 header 则跳过缓存” - 生产环境别依赖“清除 CDN 缓存按钮”——它只清边缘节点,不保证全网同步;真正稳的方式是让 URL 变(如
/fragments/comments?v=20260616) - 如果碎片 URL 不含版本参数,又无法改 CDN 配置,唯一办法是服务端返回
Cache-Control: no-store——代价是每次重下载,但至少内容绝对新鲜
模板碎片的缓存问题从来不是单一环节的事:它横跨服务端模板逻辑、HTTP 响应头、CDN 路径规则、甚至 HTMX 的请求上下文。最容易被忽略的是——碎片本身没有缓存身份,它的命运完全绑定在它被交付的方式上。要么走独立请求并配独立头,要么随主页面被统一管控,不存在中间态。



















