模板编译结果必须缓存,但execute不能缓存;真正应缓存的是template.parse()后的ast或编译函数,而template.execute()必须实时执行,否则导致hydration失效或dom mismatch。

模板编译结果必须缓存,但 execute 不能缓存
服务端渲染中最大的性能陷阱,就是把 template.Execute() 的完整 HTML 字符串塞进 Redis。这会导致用户昵称、实时时间、未读数等动态内容被固化,下次请求直接返回旧 HTML,hydration 失败或 DOM mismatch。
真正该复用的是模板解析后的中间产物:template.Parse() 返回的 AST 或编译函数(如 EJS 的 compile() 结果)。这部分不依赖运行时数据,可安全复用。
- Go 的
html/template:需手动复用同一个*template.Template实例,避免每次请求都调用ParseFS() - EJS:启用
cache: true仅缓存编译函数,不是渲染结果 - Marko:
AsyncStream内部已做函数级缓存,无需额外干预
流式渲染下,缓存必须按 chunk 切分且带 segment 标识
普通模板引擎(如 Handlebars)输出整页字符串,缓存粒度只能是「整页」或「片段」;而 AsyncStream 是生成器,支持首屏骨架、导航栏、广告位等独立缓存 —— 但前提是每个 chunk 有唯一缓存键,且不能跨 stream 生命周期拼接。
错误做法:cache.get('page') + cache.get('comments') 可能破坏流顺序或触发 double-write;正确做法是让 AsyncStream 自动控制 write 时机,每个 segment 独立缓存。
- 缓存 key 必须包含分段标识,例如
"header:en"、"footer:zh" - key 构建逻辑默认含
locale和device type,但故意排除session ID,防止缓存污染 - 一旦调用
res.write()发送首个 chunk,后续 chunk 就不能再从缓存读取后拼接 —— HTTP 流不可逆
hydration 场景下,含 data-hydrate 的节点必须剔除或剥离
如果只对部分组件做 hydration(比如仅激活搜索框和登录按钮),缓存 HTML 时若保留 data-hydrate 属性,客户端 JS 会尝试 hydrate 一个早已绑定过事件的 DOM,引发重复绑定、状态错乱或点击两次才响应。
- 典型表现:表单提交后清空、按钮点击无反馈、input 输入延迟
- 解决方案有两种:在缓存前主动移除所有
data-hydrate属性;或只缓存不含该属性的节点子树 - 构建时可通过正则或 AST 遍历过滤,不要依赖运行时 DOM 操作清理
组件化拆分时,block 命名必须全局唯一且语义化
art-template 等支持继承的引擎里,多层 layout 继承(layout → inner-layout → page)容易因 block 名重复导致中间层插入点失效。比如多个子模板都定义 {{block 'content'}},最末层会覆盖上级所有同名 block。
- 命名建议按区域+职责,如
'header-nav'、'main-hero'、'sidebar-recommend' - 所有 block 名应在项目根目录统一维护为 JSON 配置,供构建脚本校验
- 继承深度超过三层(layout → feature → detail)会显著降低编译缓存命中率,调试也更困难
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











