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

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状态不一致。

  • 具体解决办法

  1. 固定统计数组的顺序:不要依赖统计时的自然顺序,手动指定状态的固定排序,示例代码:

    // 定义固定的状态优先级顺序
    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的顺序如何,生成的数组顺序始终统一,服务端与客户端的渲染结果就能匹配。

  2. 确保服务端与客户端的feedbacks完全同步:检查客户端代码是否在初始化阶段(比如useEffect)修改了feedbacks,导致客户端状态与服务端初始的productRequests不一致。如果有这类操作,需要保证服务端渲染时的状态和客户端初始状态完全相同。

  3. 校验getRoadmapStatusCount逻辑:确认该函数在服务端和客户端执行时返回的数组顺序完全一致。如果是通过对象统计后转数组,注意不同环境下对象键的遍历顺序可能有差异,建议改用固定顺序的数组来生成统计结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 10:55:50