为什么 SSR 一定会有 hydration mismatch?
一、先把问题还原到最本质SSR 做了两件事服务端生成 HTML客户端接管HydrationHydration 的本质是在不重建 DOM 的情况二、关键矛盾同一份代码在两个环境执行这是问题的根源。SSR 架构本质是同一套组件逻辑 在两个环境执行 - Node服务端 - Browser客户端问题在于这两个环境永远不可能完全一致三、用一个最简单的例子说明span{new Date().getMilliseconds()}/span服务端输出span123/span客户端执行span167/span结果不一致 → mismatch (Vite-plugin-ssr)所以推导出一个结论只要渲染依赖“运行时”就必然存在不一致的可能四、为什么“不一致”是必然的从几个维度拆开看1. 时间是不一致的Date.now() new Date()服务端时间 ≠ 客户端时间网络延迟会放大差异结论只要用时间就有 mismatch 风险 (Vite-plugin-ssr)2. 环境是不一致的服务端没有window document localStorage客户端有典型代码if (typeof window ! undefined) { // client logic }结果服务端渲染 A客户端渲染 B直接 mismatch3. 状态是不一致的例如if (localStorage.getItem(token)) { return Home / } else { return Login / }服务端没有 localStorage → Login客户端有 token → Home结果DOM 完全不同4. 随机性是不一致的Math.random() uuid()服务端生成一套客户端再生成一套结论不可能一致5. 执行顺序是不一致的例如async 数据并发渲染不稳定 ID 生成再次重复一开始提到的前提SSR 本质是“重复计算”而重复计算无法保证一致性五、为什么框架“必须要求一致”问题来了为什么一定要一致因为 Hydration 的优化前提是复用已有 DOM 而不是重新创建如果不一致框架只能丢弃 DOM重新渲染这会导致闪屏性能下降交互延迟 (原理详解)一个更本质的矛盾SSR 想要提前生成 HTML提高首屏但 Hydration 需要再执行一遍渲染逻辑这本身就是一次“重复执行系统”而所有重复执行系统都有一个问题一致性无法保证六、工程上怎么“缓解”但不是“解决”mismatch 只能减少无法彻底消灭常见手段1. 保证初始数据一致// server 计算 const data fetch() // 注入 HTML window.__INITIAL_DATA__ data2. 延迟客户端逻辑useEffect(() { // client only }, [])3. 避免不确定性不要在 render 中用DateMath.random浏览器 API4. 架构层优化例如Islands ArchitecturePartial Hydration本质是减少需要 hydration 的范围七、讨论一下 Islands为什么这个架构本质是在“逃避 hydration”先看传统 SSR 流程Server Render HTML ↓ Browser 接收 HTML ↓ 整页 Hydration ↓ 页面可交互问题在于页面里很多内容其实根本不需要交互。例如一个电商详情页商品标题商品描述banner评论文本footer这些内容需要 SEO需要首屏展示但不需要 JS 接管然而传统 SSR 会做什么即使这些内容完全静态也要执行 hydration。也就是说静态内容 动态内容 全部进入 hydration这就带来两个问题1. 不必要的 JS 执行例如页面结构div Header ProductInfo Comments BuyButton Footer /div真正需要交互的可能只有BuyButton但传统 hydrationHeader hydrate ProductInfo hydrate Comments hydrate BuyButton hydrate Footer hydrate本质上为了一个按钮整页都要重复执行一遍。这很浪费。2. mismatch 风险被放大前面说过hydration 本质是重复计算那只要重复计算范围越大时间差异环境差异状态差异都会扩大。整页 hydration 意味着整页都有 mismatch 风险。Islands 的思路不要整页 hydrationIslands 架构把页面拆成Static HTMLInteractive Islands例如Header / ProductInfo / BuyButton client:load / Footer /只有BuyButton需要客户端接管。其余部分只保留 HTML永不 hydration流程变成Server Render HTML ↓ 静态部分直接展示 ↓ 仅局部组件 hydration本质变化传统 SSRHTML 先渲染JS 再接管整页IslandsHTML 默认静态只有少数区域需要 JS所以它的核心思想不是“怎么更高效地 hydration”而是尽量少 hydration甚至不 hydration这就是为什么说Islands 本质是在逃避 hydration。不是因为 hydration 做得不好而是因为hydration 天然昂贵且天然存在一致性问题。既然如此最好的办法不是优化它而是减少它。

相关新闻