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

Next 14使用自定义useIsMobile钩子出现React水合错误求助

关于Next.js 144中useIsMobile钩子的Hydration错误问题

我一直用自定义React钩子useIsMobile判断视口宽度是否小于阈值,之前所有项目都正常。最近用Next 14建新项目时,出现了这个错误:

Error: Text content does not match server-rendered HTML.

调试后发现问题出在这个钩子上:服务端预渲染的是“移动端版本”内容,但客户端首次渲染的是正确的“桌面端版本”,导致服务端和客户端的React树不匹配。就算加了'use client'指令也没用,甚至CSS媒体查询偶尔也会触发这个错误,直接被忽略。

以下是我使用的钩子代码及调用示例:

useWindowSize钩子代码

'use client';
import { useState, useEffect } from 'react';

const useWindowSize = () => {
  const [windowSize, setWindowSize] = useState({
    width: typeof window !== 'undefined' ? window?.innerWidth : null,
    height: typeof window !== 'undefined' ? window?.innerHeight : null,
  });

  useEffect(() => {
    const handleResize = () => {
      if (typeof window !== 'undefined') {
        setWindowSize({
          width: window?.innerWidth,
          height: window?.innerHeight,
        });
      }
    };

    window?.addEventListener('resize', handleResize);

    handleResize();

    return () => window?.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
};

export default useWindowSize;

useIsMobile钩子代码

'use client';
import { useState, useEffect } from 'react';
import useWindowSize from './useWindowSize';

const useIsMobile = (breakpoint: number = 768): boolean => {
  const { width } = useWindowSize()!;
  const [isMobile, setMobile] = useState<boolean>(
    width! <= (breakpoint || 768)
  );

  useEffect(() => {
    setMobile(width! <= (breakpoint || 768));
  }, [width, breakpoint]);

  return isMobile;
};

export default useIsMobile;

调用示例

const isMobile = useIsMobile(500);

return (
  <div className={styles.nav}>
    {!isMobile && <div className={styles.logo}>Logo</div>}

修复方案

核心问题

服务端渲染时没有window对象,初始width为null,而null <= 数字在JS中会被判定为true,导致服务端渲染移动端内容;但客户端初始化时window.innerWidth是真实的桌面宽度,首次渲染为桌面端,直接触发Hydration不匹配。

具体修复步骤

  1. 调整useWindowSize的初始状态
    服务端直接将初始尺寸设为undefined,避免服务端与客户端初始值差异,仅在客户端通过useEffect获取真实窗口尺寸:

    'use client';
    import { useState, useEffect } from 'react';
    
    const useWindowSize = () => {
      const [windowSize, setWindowSize] = useState<{ width?: number; height?: number }>({});
    
      useEffect(() => {
        const handleResize = () => {
          setWindowSize({
            width: window.innerWidth,
            height: window.innerHeight,
          });
        };
    
        window.addEventListener('resize', handleResize);
        handleResize(); // 客户端初始化时立即获取尺寸
    
        return () => window.removeEventListener('resize', handleResize);
      }, []);
    
      return windowSize;
    };
    
    export default useWindowSize;
    
  2. 修改useIsMobile的状态逻辑
    初始状态设为undefined,直到客户端获取到真实窗口尺寸后再更新状态,避免服务端和客户端首次渲染内容不一致:

    'use client';
    import { useState, useEffect } from 'react';
    import useWindowSize from './useWindowSize';
    
    const useIsMobile = (breakpoint: number = 768): boolean | undefined => {
      const { width } = useWindowSize();
      const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);
    
      useEffect(() => {
        if (width !== undefined) {
          setIsMobile(width <= breakpoint);
        }
      }, [width, breakpoint]);
    
      return isMobile;
    };
    
    export default useIsMobile;
    
  3. 组件中处理未初始化状态
    在调用钩子的组件里,先判断isMobile是否为undefined,可以显示空占位或加载态,等客户端获取尺寸后再切换到对应内容:

    const isMobile = useIsMobile(500);
    
    // Hydration阶段先渲染兼容服务端的占位内容
    if (isMobile === undefined) {
      return <div className={styles.nav}></div>;
    }
    
    return (
      <div className={styles.nav}>
        {!isMobile && <div className={styles.logo}>Logo</div>}
      </div>
    );
    
  4. 优先使用CSS媒体查询替代JS判断
    如果只是控制元素显示隐藏,尽量用CSS媒体查询,避免Hydration问题:

    .logo {
      display: block;
    }
    
    @media (max-width: 500px) {
      .logo {
        display: none;
      }
    }
    

    这种方式下服务端和客户端渲染的HTML结构完全一致,仅通过CSS控制显示,不会触发Hydration错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:07:46