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
相关产品推荐
相关产品推荐

