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

使用next-themes实现暗色模式时出现Hydration失败错误

解决next-themes实现暗色模式时的Hydration不匹配错误

问题场景

我尝试用next-themes实现暗色模式,由于layout.tsx中的<body>设置了背景色,需要让<ThemeProvider>包裹<body>来同步切换背景色。为避免整个应用变为客户端组件,我从./lib/providers.ts导入已标记为客户端组件的<ThemeProvider>,但出现以下错误:

  • ❌ 错误:Hydration failed because the initial UI does not match what was rendered on the server.
  • ⚠️ 警告:Expected server HTML to contain a matching <body> in <html>.

我尝试创建单独的<Providers>组件包裹<body>,但问题依旧。

相关代码

providers.ts

"use client";

export { ThemeProvider } from "next-themes";

原layout.tsx

import Navbar from "@/components/Navbar";
import "./globals.css";
import type { Metadata } from "next";
import { Nunito_Sans } from "next/font/google";
import Sidebar from "@/components/Sidebar";
import { ThemeProvider } from "@/lib/providers";

const nunito_sans = Nunito_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "My name | Portfolio",
  description: "My moto",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <ThemeProvider>
        <body
          className={`${nunito_sans.className} bg-gradient-to-r from-green to to-blue-400`}
        >
          <main className="grid grid-cols-12 gap-6 my-14 px-5 lg:px-48 sm:px-20 md:px-32">
            <div className="col-span-12 lg:col-span-3 bg-white rounded-2xl p-4 text-center">
              <Sidebar />
            </div>
            <div className="col-span-12 lg:col-span-9 bg-white rounded-2xl flex flex-col overflow-hidden">
              <Navbar />
              {children}
            </div>
          </main>
        </body>
      </ThemeProvider>
    </html>
  );
}

尝试的独立<Providers>组件

"use client";

import { ThemeProvider } from "next-themes";

export default function Providers({ children }: { children: React.ReactNode }) {
  return <ThemeProvider>{children}</ThemeProvider>;
}

解决方案

错误根源是客户端组件不能作为服务端渲染元素(如<body>)的父元素,服务端渲染时不会输出客户端组件的结构,导致客户端 hydration 时结构不匹配。按以下步骤修改:

1. 配置ThemeProvider的属性

修改providers.ts,给ThemeProvider添加attribute="class"配置(让主题切换通过给<html>添加类实现),同时设置默认主题:

"use client";

import { ThemeProvider } from "next-themes";

export default function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider 
      attribute="class" 
      defaultTheme="light" 
      enableSystem={true}
    >
      {children}
    </ThemeProvider>
  );
}

2. 调整layout.tsx的结构

将<Providers>放在<body>内部,包裹所有页面内容,同时把body的背景色样式移到全局CSS中:

import Navbar from "@/components/Navbar";
import "./globals.css";
import type { Metadata } from "next";
import { Nunito_Sans } from "next/font/google";
import Sidebar from "@/components/Sidebar";
import Providers from "@/lib/providers";

const nunito_sans = Nunito_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "My name | Portfolio",
  description: "My moto",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${nunito_sans.className}`}>
        <Providers>
          <main className="grid grid-cols-12 gap-6 my-14 px-5 lg:px-48 sm:px-20 md:px-32">
            <div className="col-span-12 lg:col-span-3 bg-white rounded-2xl p-4 text-center dark:bg-gray-800">
              <Sidebar />
            </div>
            <div className="col-span-12 lg:col-span-9 bg-white rounded-2xl flex flex-col overflow-hidden dark:bg-gray-800">
              <Navbar />
              {children}
            </div>
          </main>
        </Providers>
      </body>
    </html>
  );
}

3. 在全局CSS中定义主题切换的背景色

修改globals.css,添加dark:前缀的暗色模式样式(需确保Tailwind已开启class模式的暗色支持):

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  @apply bg-gradient-to-r from-green to-blue-400 dark:bg-gradient-to-r dark:from-gray-900 dark:to-gray-800;
}

4. 开启Tailwind的class暗色模式

在tailwind.config.ts中设置darkMode: 'class':

import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: 'class',
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

export default config

这样修改后,服务端渲染的HTML结构和客户端hydrate后的结构一致,同时能正常触发暗色模式的样式切换,解决hydration错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 19:45:14