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

