如何解决Next.js中客户端渲染结合useEffect引发的React Hydration错误
解决方案:避免Next.js React Hydration Error的客户端渲染实践
问题根源
你的useWindowSize钩子在服务端渲染阶段会执行updateWindowSize(),但服务端不存在window对象,且初始state设为[0,0],导致服务端输出的HTML与客户端首次渲染的DOM结构不匹配,触发hydration错误。
核心修复方案
1. 隔离客户端专属逻辑
所有依赖浏览器API(如window)的操作必须延迟到客户端渲染阶段执行——useEffect钩子仅在客户端触发,是实现这一点的标准方式。
2. 修正useWindowSize钩子
调整初始state为null,避免服务端渲染时生成与客户端不一致的DOM;仅在useEffect中初始化窗口尺寸并绑定监听事件。
优化后的代码示例
修正后的useWindowSize钩子
"use client" import { useState, useEffect } from 'react'; function useWindowSize(nav, setNav) { // 服务端渲染时返回null,避免初始值不匹配 const [windowSize, setWindowSize] = useState(null); useEffect(() => { function updateWindowSize() { const width = window.innerWidth; const height = window.innerHeight; setWindowSize([width, height]); if (width >= 768 && nav) { setNav(false); } } window.addEventListener('resize', updateWindowSize); updateWindowSize(); return () => window.removeEventListener('resize', updateWindowSize); }, [nav, setNav]); return windowSize; }
调整Navbar组件逻辑
在组件中处理windowSize为null的情况,服务端渲染时默认使用桌面端布局,确保前后输出一致:
const Navbar = () => { const [nav, setNav] = useState(false); const windowSize = useWindowSize(nav, setNav); // 服务端渲染时默认设为非移动端,客户端获取尺寸后自动更新 const isMobile = windowSize ? windowSize[0] < 768 : false; // 其余原有逻辑保持不变 const { scrollY } = useScroll(); const [hidden, setHidden] = React.useState(false); function update() { if (scrollY?.current < scrollY?.prev) { setHidden(false); } else if (scrollY?.current > 100 && scrollY?.current > scrollY?.prev) { setHidden(true); } } useEffect(() => { return scrollY.onChange(() => update()); }, [scrollY]); return ( <motion.nav variants={variants} initial="hidden" animate={hidden ? 'hidden' : 'visible'} transition={{ ease: [0.1, 0.25, 0.3, 1], duration: 0.6 }} className={`navbar-bg dark:bg-gray-900 fixed w-full z-20 top-0 left-0`} > <div className="absolute w-[20%] inset-0 gradient-01" /> <div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"> <Link href="/"> <Image src={logo} alt="/" className="cursor-pointer" width="175" height="175" /> </Link> {/* 其余渲染内容 */} </div> </motion.nav> ); };
最佳实践总结
- 所有依赖浏览器环境的代码必须包裹在
useEffect中,或通过typeof window !== 'undefined'判断后执行,绝对不能在组件顶层直接访问window/document。 - 服务端渲染的初始state要尽可能与客户端首次渲染的状态对齐,或在客户端初始化前显示兼容的默认布局。
- 避免在hydration阶段修改DOM状态,所有状态更新必须延迟到客户端挂载完成后。
内容的提问来源于stack exchange,提问作者anonauditor
相关产品推荐
相关产品推荐

