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

Next.js中能否用useContext动态设置className?出现类名不匹配警告

解决Next.js中useContext动态className导致服务端与客户端不匹配的警告

问题场景

子组件基于useContext的值动态渲染className后,Next.js抛出如下警告:

Warning: Prop className did not match. Server: "some-class some-other-class " Client: "some-class some-other-class dark-theme"

问题原因清晰:服务端渲染时useDarkTheme为false,但客户端用户交互后该值变为true,导致前后渲染的className不一致。

示例代码

子组件代码

'use client';

import { useContext } from "react";
import { CustomThemeContext, CustomThemeProviderContext } from "@/contexts/custom-theme";
import Link from "next/link";
import './styles.scss';

export default function SomeComponent() {

  const { useDarkTheme, setUseDarkTheme } = useContext<CustomThemeProviderContext>(CustomThemeContext);

  const themeClassName = useDarkTheme ? 'dark-theme' : '';

  return (
      <h1 className={`some-class some-other-class ${themeClassName}`}>
        Welcome
      </h1>
  );
}

Context类型定义

export interface CustomThemeProviderContext {
  useDarkTheme: boolean;
  setUseDarkTheme: (useDarkTheme: boolean) => void;
}

Context创建代码

export const CustomThemeContext: any = createContext<CustomThemeProviderContext>({
  useDarkTheme: false,
  setUseDarkTheme: (useDarkTheme: boolean): void => {
  }
});

解决方案

方法1:服务端同步用户主题偏好(推荐)

如果用户的主题偏好存储在Cookie或数据库中,服务端渲染时先读取该值,确保服务端与客户端初始状态一致。

  1. 在layout.tsx(或页面入口组件)中读取Cookie:
import { cookies } from 'next/headers';
import { CustomThemeProvider } from '@/contexts/custom-theme';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const cookieStore = cookies();
  // 从Cookie中获取已保存的主题设置
  const savedTheme = cookieStore.get('theme')?.value === 'dark';
  
  return (
    <html lang="en">
      <body>
        <CustomThemeProvider initialTheme={savedTheme}>
          {children}
        </CustomThemeProvider>
      </body>
    </html>
  );
}
  1. 修改CustomThemeProvider,用传入的初始值初始化状态:
'use client';

import { createContext, useState } from 'react';
import type { CustomThemeProviderContext } from './types';

export const CustomThemeContext = createContext<CustomThemeProviderContext>({
  useDarkTheme: false,
  setUseDarkTheme: () => {},
});

export function CustomThemeProvider({ 
  children, 
  initialTheme = false 
}: { 
  children: React.ReactNode; 
  initialTheme?: boolean;
}) {
  const [useDarkTheme, setUseDarkTheme] = useState(initialTheme);

  return (
    <CustomThemeContext.Provider value={{ useDarkTheme, setUseDarkTheme }}>
      {children}
    </CustomThemeContext.Provider>
  );
}

方法2:客户端挂载后再应用主题

如果无法在服务端获取主题偏好,可通过useEffect延迟客户端主题切换,确保服务端渲染内容与客户端初始渲染一致:

'use client';

import { useContext, useEffect, useState } from "react";
import { CustomThemeContext, CustomThemeProviderContext } from "@/contexts/custom-theme";
import Link from "next/link";
import './styles.scss';

export default function SomeComponent() {
  const { useDarkTheme } = useContext<CustomThemeProviderContext>(CustomThemeContext);
  const [clientTheme, setClientTheme] = useState(false);

  useEffect(() => {
    // 客户端挂载完成后同步主题状态
    setClientTheme(useDarkTheme);
  }, [useDarkTheme]);

  const themeClassName = clientTheme ? 'dark-theme' : '';

  return (
      <h1 className={`some-class some-other-class ${themeClassName}`}>
        Welcome
      </h1>
  );
}

注:此方法可能存在短暂的主题闪烁,但可解决警告问题。

方法3:禁用组件SSR

将组件设置为仅客户端渲染,完全跳过服务端渲染环节:

// 在父组件中导入目标组件
import dynamic from 'next/dynamic';

const SomeComponent = dynamic(() => import('./SomeComponent'), { ssr: false });

注:此方法会失去SSR的性能和SEO优势,仅适合非核心内容组件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:40:18