局部同构更新不能实现“不重刷页面却更新SEO”,SEO内容必须由服务端直出并存在于初始HTML中;局部刷新仅能安全更新与服务端输出严格匹配的子树,不得破坏已有SEO结构。
局部同构更新本身不能“不重刷页面却更新 SEO”——这是个根本性误解。SEO 内容必须在初始 HTML 响应中存在,爬虫不会等待 JS 执行、不触发 React 更新、不读取客户端动态注入的 title 或 meta。所谓“局部更新 SEO”,实际是让局部刷新区域**不破坏已有的 SEO 内容**,而非靠它生成新 SEO。
React 在 hydration 时会逐节点比对服务端输出和客户端虚拟 DOM。一旦 mismatch(比如服务端渲染了 <h1>产品介绍</h1>,客户端初始 state 却是空字符串),React 就丢弃整块服务端 HTML,重新 mount,导致标题、描述、结构化数据全部丢失。
key,例如 key={article.slug};禁用 Math.random()、数组索引、时间戳等不可预测值null,客户端初始 state 不能是 undefined(null !== undefined)window.__INITIAL_DATA__ = {"article": {...}},客户端直接读取,避免二次解析偏差搜索引擎爬虫只解析初始 HTML 响应体,不执行 useEffect,不等待 Suspense,不识别 React.lazy 加载的内容。任何依赖客户端 JS 才能呈现的标题、摘要、链接、og:title,对 SEO 来说等于不存在。
<title> 和 <meta name="description"> 必须在服务端响应中写死,不能靠 document.title = 动态改og:description)、JSON-LD 结构化数据、hreflang 声明,都需服务端拼入 HTML,不可客户端插入loadable 或 Suspense 中所谓“组件级局部刷新”,本质是划定一个 hydration 后由 React 全权控制、且与服务端输出完全一致的 DOM 子树。这个子树之外的内容,仍是静态 HTML,承担 SEO 职责。
<section aria-live="polite">...</section>,并添加 <ErrorBoundary> 捕获 hydration 失败store.setState(__INITIAL_DATA__.store))"response includes hydratable /comments",便于监控 mismatch真正容易被忽略的点是:局部同构更新不是“让 SEO 变得更动态”,而是“不让交互破坏 SEO”。只要服务端直出的 HTML 里已有完整语义化结构、真实内容和正确元信息,局部刷新就只是在它旁边安全地动一小块——其余部分,爬虫已经看完了。