Hydration
结论摘要
本页以已启用前端和 SSR 的全栈项目为前提,比较浏览器交互开启与关闭后的行为。CSR 的空 shell 属于另一维度,见渲染模式。
Hydration 默认启用。hydration: "none" 仍会正常返回完整的 SSR HTML、CSS 和 SEO;它不会返回空页面,也不会关闭 SSR。
它移除框架生成的浏览器 runtime 与 hydration 数据。因此 React 事件、Vext Form 增强、Vext fetcher 和框架管理的客户端导航不可用;包括 Vext Form 渲染出的原生 form 在内,HTML 能力仍按浏览器行为工作。
默认 hydration 是什么
Hydration 会把浏览器端 React tree 接到 SSR 产出的 HTML 上。默认模式会加载 Vext browser entry,并将浏览器端 React tree 接到已经显示的页面上,让 Vext 的交互和客户端导航生效。
Vext 会把 render payload 写入 document,让 client entry 不需要重复执行首屏 service 调用:
- page id
- props
- layoutData
- locale 和 messages
- 初始 route 使用的 head metadata
- build id 和 route assets
默认模式与 hydration: "none" 的对比
适合与不适合使用
适合使用
- 文章详情页。
- 文档页。
- 营销页。
- SEO 内容页。
- 只需要服务端输出的页面。
- 交互由应用自己加载的独立 script 处理的页面。
不适合使用
- 后台管理页面。
- 富文本编辑器。
- 搜索、筛选、分页等 React 交互页面。
- 使用 Vext Form 或 fetcher 的页面。
- 依赖 Vext 客户端导航的页面。
为一个 SSR 页面关闭 hydration
创建以下两个文件。/article/intro 关闭 hydration,/article/interactive/intro 保留默认行为;两个 URL 使用相同页面,便于直接比较。内存文章无需额外 service,真实业务查询应放在 handler 调用的服务中。
计数按钮用于验证两种策略的差异,纯内容页面可以移除它。以下行为说明针对 /article/intro:
这个路由的行为如下:
- 首次访问仍会返回完整 SSR HTML。
- 页面可以正常显示并加载 CSS。
- 普通
<a>链接和普通 HTML<form>仍然可用。 - React
onClick等事件不会执行。 - Vext Form 的增强、fetcher 和框架管理的同 document 导航不会执行。
- 从
none页面进入 hydration 页面时,需要完整 document navigation;进入目标页面后,hydration 会恢复。
用户自己写入 document 的独立 script 也会保留;是否工作取决于脚本自身,不依赖 Vext runtime。
关闭后失去的能力
hydration: "none" 页面中没有 Vext/React browser runtime,因此不能依赖框架接管页面后的行为:
- React 事件处理和依赖 React state 的交互不会运行。
- Vext Form 不会接管或增强表单。
- Vext fetcher 不会发起框架管理的客户端请求。
- Vext 不会管理同 document 客户端导航。
如果页面仍需要交互,请保留默认 hydration,或让页面使用与 Vext runtime 无关、由你自己加载和维护的独立 script。
整页作用范围和当前限制
hydration: "none" 作用于整个 document,不能只关闭某个 React 组件的 hydration。当前不能只 hydrate 搜索框、评论区或其他局部区域。
它要求 SSR 保持开启,不能与路由 clientOnly: true、全局 frontend.render.ssr: false 或单次 ssr: false 组合;当前 no-hydration 路径也关闭 streaming。
当前公开能力也不宣称支持 Selective/Partial Hydration、Islands、React Server Components 或 Partial Prerendering(PPR)。不要把这个路由级开关理解为局部 hydration 机制。
为什么当前没有全局配置
当前公开 API 没有全局 hydration: "none" 配置。同一个应用可以同时包含需要交互的页面和纯 SSR 页面;如果全局关闭,所有页面都会失去 React/Vext 客户端能力。
如果整个站点都需要纯 SSR,请逐个路由声明 hydration: "none"。应用可以生成满足静态语法的源文件,但不能使用下节所述的不透明 route options helper 调用隐藏最终策略;源码生成也不等于 Vext 提供了全局配置 API。
Route options 的静态语法
Vext 会在构建阶段读取每个路由的 hydration policy,并据此生成 route manifest 和资源清单。直接内联声明是最简单的受支持形式:
有限静态语法也接受同文件 const 对象与 TypeScript 静态包装。route options helper 调用会被拒绝,因为索引无法安全执行其函数体或确认最终合同;请内联最终对象,或直接传入保存最终对象的同文件 const。索引不会执行导入值、计算表达式或带插值的模板字符串;无法投影的 path 或被索引 schema 会携带 route 上下文失败,而不是静默遗漏。依赖请求数据的动态页面元数据继续放在 res.render(..., { seo })。
避免 Mismatch
保持 SSR 与浏览器输出确定。关闭 SSR、clientOnly: true 或客户端 fallback 的空 shell 会使用 createRoot;完成 SSR 的页面使用 hydrateRoot,即使组件返回 null。模式验证见 CSR 挂载行为。
Hydration 标记
Vext 提供低噪音诊断标记:
hydration: "none" 的 document 会标记为 data-vext-hydration="none",以便诊断该页面是有意不加载 browser runtime。生产环境不需要默认输出 console 性能日志;验证脚本读取 DOM 与 Performance API。
done 由根 hydration boundary 的 effect 标记;Performance entry 依赖浏览器 Performance API 可用。这些信号说明 runtime 已到达相应阶段,不能单独证明无 mismatch、所有异步内容已完成或交互正确,仍要验证页面行为。
Route Assets
Render manifest 会记录每个 route 的 initial JS/CSS。默认 hydration 的 SSR 可以注入 route-specific modulepreload,避免 hydration 后才发现 page chunk;hydration: "none" 不输出这些 route JS preload,但不会移除 CSS。
如果生产 vext start 发现 manifest 过旧且缺少 route assets,会 fail fast 并提示重新构建。
验证
在应用根目录执行 npm run build,通过后启动 npm start -- --port 3000。分别直接打开本页两个 URL:两者都应已有文章正文和标题;/article/intro 的按钮停留在 Clicks: 0,交互 URL 的按钮应能增加计数。两者的普通链接和 GET 表单都应正常工作。按Hydration 验证继续检查 root、数据/入口脚本、preload 与 Performance entry,结束后停止服务。
维护本仓库文档时
修改本仓库文档后,运行文档契约检查:
这个命令只检查仓库文档契约,不是浏览器 runtime 测试。验证应用行为时,请使用应用自己的 build、start 和浏览器测试流程,并参照 Hydration 验证。
默认 hydration route 应出现 browser entry、route JS preload、data-vext-hydration="done" 和 vext:hydration Performance entry。none route 应保留 SSR HTML/CSS/SEO 并出现 data-vext-hydration="none",但不应出现 Vext browser entry、__VEXT_DATA__、route JS preload、done marker 或 hydration Performance entry。