html模板碎片缓存必须服务端实现,因浏览器/cdn仅按url缓存,无法感知user.id等上下文变量;应使用map管理缓存键以避免冲突,注入唯一fragmentid防hydration冲突,并通过业务信号(如redis pub/sub)精准失效。

HTML模板碎片不能靠浏览器缓存来“省事”,真正要优化的是服务端渲染时的模板编译与片段缓存——浏览器只该管 JS/CSS,不该管 HTML 结构本身。
为什么Cache-Control对模板碎片无效
你给/fragments/header.html接口配了public, max-age=3600,CDN 看着缓存住了,但用户刷新后看到的还是旧昵称。这不是缓存没生效,而是它本就不该生效:模板碎片通常依赖请求上下文(如user.id、locale、ab_test_group),而浏览器或 CDN 无法感知这些变量变化。
-
浏览器缓存键只认 URL,
/fragments/header.html?user_id=123和?user_id=456在 HTTP 层是两个资源,但服务端可能共用同一模板+不同数据——缓存 URL 就等于缓存错误组合 - CDN 默认忽略
Cookie、Authorization、HX-Request等 header,根本没法做上下文隔离 -
ETag或Last-Modified只能反映文件变更,无法反映数据内容变更(比如 CMS 更新了页脚文案,但模板文件没动)
服务端模板碎片该用Map还是{}缓存
用Map。不是“更高级”,而是避免 key 冲突的刚需。
- 路径含查询参数时,
{}会把header.html?theme=dark和header.html?theme=light都转成字符串"[object Object]"或归一化为同一 key - Unicode 路径(如
nav.中文.html)在{}里 toString 后可能乱码,Map原样保留 - 需要按依赖关系清理缓存时(比如改了
footer.html,得连带清掉所有include它的父模板),Map支持forEach和clear(),{}遍历顺序不可靠 - Marko、HTM 等生产级引擎源码里都用
new Map()存编译结果,不是巧合
如何让模板碎片缓存不污染 hydration
ID 冲突不是“偶尔出错”,而是必然发生——只要多个服务或组件返回同名id="loading",hydration 就会绑定错节点。
- 服务端渲染时,对每个碎片注入唯一前缀:
id="{{.FragmentID}}-modal-root",而不是写死id="modal-root" - 客户端 hydrate 前,用
document.querySelectorAll('[id^="{{.FragmentID}}-"]')批量重写 ID,再挂逻辑 - 禁止在模板里用
id="sidebar"这类通用名;哪怕只在一个碎片里用,聚合渲染时也大概率重复 - CDN 或反向代理若开启 HTML 自动优化(如 Cloudflare 的“Auto Minify”),会删空格、补标签,破坏 ID 前缀结构——必须关闭该功能
缓存失效信号必须来自业务层,不是时间
设max-age=300看着省事,实际等于放任 stale data 横行。首页 Banner 每天 10 点更新,但缓存可能凌晨 2 点过期,中间 8 小时全靠错内容撑着。
- 文件变更时,只清对应碎片及其直接依赖项(比如
product-card.templ变了,就清它 + 所有include它的列表页模板),别cache.clear()全盘重来 - CMS 内容更新后,发 Redis Pub/Sub 消息:
PUBLISH fragment:header:en "update",监听端按 key 清缓存 - 构建部署时,把模板哈希写入
template-hash.json,缓存 key 中包含该 hash,发布即自动失效 - 永远不要在模板里写
{{.CurrentUser.Name}}还试图缓存——含任何请求变量的片段,缓存键就必须带完整上下文维度(locale、device、role)
最常被跳过的一步:缓存键里漏掉isMobile或ab_test_group,导致桌面用户看到移动端结构,A 组用户看到 B 组按钮。这不是缓存没配好,是缓存配得太“干净”了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











