You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 12.2中基于useSWR的简单组件水合(Hydration)问题排查

解开Next.js水合问题的谜团:你的组件到底哪里错了?

嘿,我来帮你拆解这个问题——核心原因其实是服务端渲染出的HTML不符合HTML语义规范,被浏览器自动修正后,和客户端React期望的DOM结构不匹配,直接触发了水合冲突。

我们先看第一个出问题的组件:

  • 服务端渲染时,data还没拿到(SWR默认在服务端不会主动发起请求,除非你配置了预取),所以会在<tbody>里面渲染<div>Loading...</div>。
  • 但HTML规则明确规定:<tbody>的直接子元素只能是<tr>标签!浏览器遇到这种无效结构时,会自动把那个<div>移到<table>外面去。
  • 等客户端开始水合时,React的虚拟DOM还以为<tbody>里有个loading的div,但真实DOM里这个div已经被浏览器挪走了——虚拟DOM和真实DOM对不上,水合错误自然就出现了。

再看第二个能正常运行的组件:

  • 当data不存在时,组件直接返回纯文本"loading...",完全不渲染<table>相关的任何结构。
  • 服务端输出的是纯文本,客户端水合时也是同样的内容,两者结构100%匹配,自然不会有冲突。

怎么修复第一个组件?

你不用完全放弃部分渲染,只要把loading内容放在符合语义的位置就行:

方案1:把loading放在表格外面

return (
  <div className={styles["my-component"]}>
    {data == undefined ? (
      <div>Loading...</div>
    ) : (
      <table>
        <tbody>
          <tr>
            <th>Title</th>
            <th>Description</th>
          </tr>
          {data.map((a: any) => (
            <tr key={a.uid}>
              <td>{a.title}</td>
              <td>{a.description}</td>
            </tr>
          ))}
        </tbody>
      </table>
    )}
  </div>
);

方案2:在tbody内用符合语义的标签展示loading

{data == undefined ? (
  <tr>
    <td colSpan={2}>Loading...</td>
  </tr>
) : (
  data.map((a: any) => (
    <tr key={a.uid}>
      <td>{a.title}</td>
      <td>{a.description}</td>
    </tr>
  ))
)}

最后总结一下:水合错误很多时候都是因为服务端输出的HTML不规范,被浏览器偷偷修改了结构,导致React的虚拟DOM和真实DOM对不上。你的第一个组件刚好踩了这个tbody的语义坑,而第二个组件通过提前返回避免了无效DOM的生成,所以就正常了。

内容的提问来源于stack exchange,提问作者Dash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 13:02:29