直接innerhtml注入状态对象会失败,因未转义的'、"、等字符会导致脚本截断;必须双重转义json并用serialize-javascript等库处理,同时保证dom结构、属性顺序、文本节点完全一致以确保hydration精确对齐。

服务端预渲染 HTML 时,状态不能靠字符串拼接或手动写入 script 标签塞进 window.__INITIAL_STATE__ 就完事——这会导致 hydration 失败、React/Vue 水合错位、甚至 SSR 与 CSR 渲染结果不一致。
为什么直接 innerHTML 注入状态对象会失败
浏览器解析 HTML 时,<script>window.__INITIAL_STATE__ = { ... }</script> 中的 JSON 若含未转义的 '、"、 字符串,会被提前截断。比如用户昵称是 Jack & Jill,不编码就直接插入,JS 解析会在 & 处中断;更危险的是,若数据里混入 ,整个后续脚本都会被忽略。
- 必须对状态对象做 JSON 序列化 + HTML 实体双重转义,不能只用
JSON.stringify() - 推荐用
JSON.stringify(state).replace(/, '\u003c').replace(/>/g, '\u003e').replace(/'/g, '\u0027'),再包裹进<script></script> - 更稳妥的做法是使用现成库,如 Node.js 的
serialize-javascript,它自动处理嵌套引号、Unicode 控制字符和终止标签
如何让预渲染 HTML 与客户端 hydration 精确对齐
hydration 不只是“把 JS 挂上去”,而是要求 DOM 结构、属性顺序、文本节点位置完全一致。哪怕服务端多一个空格、少一个布尔属性(如 disabled 没写值),React 都可能放弃复用而重新挂载整棵子树。
- 服务端模板引擎必须禁用自动空格压缩(如 EJS 的
而非 <code> 输出变量) - 避免在服务端用
innerHTML = htmlStr动态注入内容——它会触发浏览器重解析,破坏原始 AST 结构 - 所有动态属性(如
class、data-id)必须由模板引擎原生支持条件拼接,而非 JS 字符串拼接后传入 - 时间类字段(如
new Date().toISOString())需在服务端统一冻结为字符串,否则客户端 hydration 时时间戳不同,diff 会误判为变更
状态解耦:把数据获取与 HTML 渲染逻辑彻底分离
常见反模式是把 fetch() 和 renderToString() 写在同一个函数里,导致无法缓存、无法 mock、无法单元测试。真正可维护的 SSR 架构,数据获取必须能独立执行、带明确超时与错误边界。
- 定义清晰的数据获取入口,例如
getServerSideData({ req, params }),返回 Promise> - HTML 模板只接收纯数据对象,不做任何异步操作;模板内禁止出现
await或fetch - 对同一页面,服务端与客户端调用同一份
getServerSideData(客户端用于 fallback 或 revalidate),保证数据 schema 一致 - 若用 React,确保
getServerSideProps返回的数据结构与客户端useSWR的 key 匹配,避免 hydration 后立即触发二次请求
最易被忽略的一点:服务端生成的 HTML 必须包含与客户端运行时完全相同的 nonce 值(用于 CSP),且该 nonce 必须随每次请求唯一生成、不可复用。否则即使状态注入正确,脚本也会被浏览器拦截,hydration 根本不会开始。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











