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

