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

如何解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 07:57:15