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

Next.js页面重载时Header组件Hydration失败求替代方案

问题

开发Next.js网站(由Header和主体构成)时,首次加载无Hydration错误,但重载页面后Header组件出现「Hydration failed because the initial UI does not match what was rendered on the server」错误。

项目通过useQuery获取数据动态生成Header,首次加载正常,但重载时数据会先返回空对象{},之后才更新正确数据,从而触发Hydration错误。

已尝试多种在线方案未解决,虽可通过动态导入组件并设置ssr:false解决,但并非期望方案。推测问题源于Header组件中的activeCompany变量——页面重载时无数据返回,该变量会临时为空对象。相关代码如下:

import { useRouter } from 'next/router';
import React, { useMemo } from 'react';

import dynamicRoute from '@/miscellaneous/constant';
import useCompany from '@/utils/hooks/company/useCompany';
import useMutations from '@/utils/hooks/mutations/useMutations';

import LinkDropdown from '../LinkDropdown';

function CompaniesDropDown() {
  const router = useRouter();
  const { userCompanies, activeCompany, isLoading } = useCompany();

  .... (some code in here)

  return (
    <div className="relative flex items-center">
      <LinkDropdown
        style={{
          fontWeight: '400',
        }}
        checkedId={activeCompany?.id}
        color="black"
        link
        onDelete={handleDelete}
        onAddClick={handleCreateCompany}
        items={items}
        header={activeCompany?.name || (router.query?.company as string)}
        onLinkClick={(item: any) => console.log('route to', item)}
        text="Add new company"
        direction="bottom left"
      />
    </div>
  );
}

export default CompaniesDropDown;

求其他替代解决方案?

解决方案

1. 利用isLoading统一服务端与客户端初始渲染内容

服务端渲染时useQuery的isLoading为true,客户端 hydration 阶段也保持一致。在加载完成前渲染与服务端匹配的占位内容,避免前后UI不匹配:

function CompaniesDropDown() {
  const router = useRouter();
  const { userCompanies, activeCompany, isLoading } = useCompany();

  // 加载中时渲染占位容器,确保服务端和客户端初始输出一致
  if (isLoading) {
    return (
      <div className="relative flex items-center">
        <div className="h-8 w-32 bg-gray-100 rounded"></div>
      </div>
    );
  }

  // 确认activeCompany有有效数据后再渲染组件
  if (!activeCompany || Object.keys(activeCompany).length === 0) {
    return (
      <div className="relative flex items-center">
        <span className="text-gray-500">选择公司</span>
      </div>
    );
  }

  .... (some code in here)

  return (
    <div className="relative flex items-center">
      <LinkDropdown
        style={{
          fontWeight: '400',
        }}
        checkedId={activeCompany.id}
        color="black"
        link
        onDelete={handleDelete}
        onAddClick={handleCreateCompany}
        items={items}
        header={activeCompany.name || (router.query?.company as string)}
        onLinkClick={(item: any) => console.log('route to', item)}
        text="Add new company"
        direction="bottom left"
      />
    </div>
  );
}

2. 修正activeCompany初始值为null

修改useCompany Hook,将activeCompany的初始状态设为null而非空对象,明确判断数据是否就绪:

// 在useCompany Hook中调整初始值
const useCompany = () => {
  const { data, isLoading } = useQuery(['userCompanies'], fetchUserCompanies, {
    initialData: {
      userCompanies: [],
      activeCompany: null, // 替换原空对象初始值
    },
  });

  return {
    userCompanies: data.userCompanies,
    activeCompany: data.activeCompany,
    isLoading,
  };
};

组件中简化判断逻辑:

function CompaniesDropDown() {
  const router = useRouter();
  const { userCompanies, activeCompany, isLoading } = useCompany();

  if (isLoading || !activeCompany) {
    return (
      <div className="relative flex items-center">
        <span className="text-gray-500">加载中...</span>
      </div>
    );
  }

  .... (some code in here)

  return (
    // 正常渲染LinkDropdown
  );
}

3. 用useEffect延迟异步数据UI渲染

借助useEffect在客户端 hydration 完成后再渲染依赖异步数据的组件,确保服务端与客户端初始渲染内容一致:

function CompaniesDropDown() {
  const router = useRouter();
  const { userCompanies, activeCompany, isLoading } = useCompany();
  const [isHydrated, setIsHydrated] = React.useState(false);

  React.useEffect(() => {
    // hydration完成后更新状态
    setIsHydrated(true);
  }, []);

  // 服务端和初始客户端渲染时显示占位
  if (!isHydrated || isLoading || !activeCompany) {
    return (
      <div className="relative flex items-center">
        <div className="h-8 w-32 bg-gray-100 rounded"></div>
      </div>
    );
  }

  .... (some code in here)

  return (
    // 正常渲染LinkDropdown
  );
}

4. 调整useQuery缓存策略

如果数据更新频率低,设置合理的staleTime让缓存数据优先加载,避免重载时出现空对象:

// 在useCompany Hook中配置
const { data, isLoading } = useQuery(['userCompanies'], fetchUserCompanies, {
  staleTime: 5 * 60 * 1000, // 5分钟内视为新鲜数据,直接用缓存
  cacheTime: 10 * 60 * 1000, // 缓存保留10分钟
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 22:25:30