Next.js中useContext导致SSR与客户端渲染className不匹配问题求助
我正在使用Next.js静态站点生成(SSG),未在getStaticProps中获取数据,同时结合Tailwind与clsx工具函数开发。
相关组件代码:
export const Sidebar = (props: SidebarProps) => { const { feedbacks } = useContext(FeedbackContext) // 返回格式:[{status: 'planned', feedbackCount: 2}, {status: 'live', feedbackCount: 1}, ...] const roadmapStatusCount = getRoadmapStatusCount(feedbacks) // ... }
基于roadmapStatusCount渲染元素的代码:
const featureCountElements = roadmapStatusCount.map( ({ status, feedbackCount }) => { return ( <div key={status}> <span className={clsx( "inline-block h-2 w-2 rounded-full", status === "planned" && "bg-accentSecondary", status === "in-progress" && "bg-accentPrimary-800", status === "live" && "bg-primary-400" )} ></span> <dt className="grow capitalize text-secondary-700">{status}</dt> <dd className="font-bold text-secondary-800">{feedbackCount}</dd> </div> ) } )
出现错误警告:Prop className不匹配。服务端:inline-block h-2 w-2 rounded-full bg-primary-400,客户端:inline-block h-2 w-2 rounded-full bg-accentPrimary-800,背景值不一致,怀疑是status值不同或元素渲染顺序问题。
上下文定义如下,初始值productRequests来自本地静态JSON文件:
interface IFeedbackContext { feedbacks: Feedback[] setFeedbacks: Dispatch<SetStateAction<Feedback[]>> | null } export const FeedbackContext = createContext<IFeedbackContext>({ feedbacks: productRequests, setFeedbacks: null })
引入useContext后才出现此问题,想知道问题出在哪里。
核心原因:服务端与客户端的
roadmapStatusCount数组顺序不一致
Next.js SSG会在服务端完成首次渲染,客户端hydrate阶段会再次渲染页面。如果两次渲染时roadmapStatusCount的元素顺序不同,同一个key对应的元素在服务端和客户端的status就会不匹配,最终导致clsx生成的className不一致。顺序不一致的诱因
getRoadmapStatusCount如果通过遍历feedbacks统计状态,而feedbacks在服务端和客户端的遍历顺序存在差异(比如对象键遍历顺序、数组排序逻辑不同),就会让生成的统计数组顺序混乱。另外,若客户端初始化阶段对feedbacks做了隐性修改,也会导致与服务端初始的productRequests状态不一致。具体解决办法
固定统计数组的顺序:不要依赖统计时的自然顺序,手动指定状态的固定排序,示例代码:
// 定义固定的状态优先级顺序 const FIXED_STATUS_ORDER = ['planned', 'in-progress', 'live'] // 根据固定顺序生成统计结果 const roadmapStatusCount = FIXED_STATUS_ORDER.map(status => ({ status, feedbackCount: feedbacks.filter(item => item.status === status).length }))这样无论
feedbacks的顺序如何,生成的数组顺序始终统一,服务端与客户端的渲染结果就能匹配。确保服务端与客户端的
feedbacks完全同步:检查客户端代码是否在初始化阶段(比如useEffect)修改了feedbacks,导致客户端状态与服务端初始的productRequests不一致。如果有这类操作,需要保证服务端渲染时的状态和客户端初始状态完全相同。校验
getRoadmapStatusCount逻辑:确认该函数在服务端和客户端执行时返回的数组顺序完全一致。如果是通过对象统计后转数组,注意不同环境下对象键的遍历顺序可能有差异,建议改用固定顺序的数组来生成统计结果。
内容的提问来源于stack exchange,提问作者semi_92

