Next-themes首次挂载主题同步异常问题排查与修复求助
解决next-themes首次加载时导航栏图片与切换按钮图标不匹配系统主题的问题
问题详情
使用next-themes实现明暗主题切换,已完成官方配置,但首次加载时:
- 页面主题正确跟随系统设置
- 导航栏图片、切换按钮图标仍保持浅色样式,未同步匹配当前主题(比如系统深色模式下,页面是深色,但图标还是浅色)
问题原因
next-themes在客户端初始化前,theme值可能为undefined或初始默认值,而你的组件仅在mounted状态变为true时渲染一次,没有监听theme的变化,导致首次加载时无法正确获取系统主题并更新UI。
解决方案
- 监听theme变化:在
useEffect中添加theme作为依赖,当主题切换(包括首次初始化完成)时触发组件更新 - 优化mounted逻辑:确保在theme初始化完成后再渲染依赖主题的UI元素
修改后的Navbar组件
// Navbar组件代码 import Image from "next/image"; import Link from "next/link"; import React, { useState, useEffect } from "react"; import { AiOutlineClose, AiOutlineMail, AiOutlineMenu } from "react-icons/ai"; import { FaGithub, FaLinkedinIn } from "react-icons/fa"; import { BsFillPersonLinesFill } from "react-icons/bs"; import NavLogo from "../public/assets/navLogo.svg"; import DarkNavLogo from "../public/assets/darkNavLogo.svg"; import { useTheme } from "next-themes"; import ToggleButton from "./ToggleButton"; const Navbar = () => { const [nav, setNav] = useState(false); const [shadow, setShadow] = useState(false); const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { // 等待客户端初始化完成,同时监听theme变化 setMounted(true); }, [theme]); // 添加theme作为依赖 const handleNav = () => { setNav(!nav); }; useEffect(() => { const handleShadow = () => { if (window.scrollY >= 90) { setShadow(true); } else { setShadow(false); } }; window.addEventListener("scroll", handleShadow); return () => window.removeEventListener("scroll", handleShadow); // 添加清理函数避免内存泄漏 }, []); return ( <div className={ shadow ? "fixed w-full h-20 shadow-xl z-[100] ease-in-out duration-300 px-4 md:px-8 lg:px-16 bg-jelly-bean-50 dark:bg-jelly-bean-950" : "fixed w-full h-20 z-[100] px-4 md:px-8 lg:px-16 bg-jelly-bean-50 dark:bg-jelly-bean-950" } > <div className="flex justify-between items-center w-full h-full px-2 2xl:px-16 "> <Link href="/"> {mounted && ( <Image src={theme === "dark" ? DarkNavLogo : NavLogo} alt="Emre Turan - Developer Logo" width={125} height={125} className="cursor-pointer" /> )} </Link> <div> <ul className="hidden md:flex "> <li className="ml-10 text-m border-b border-transparent hover:border-current"> <Link href="/">Home</Link> </li> <li className="ml-10 text-m border-b border-transparent hover:border-current"> <Link href="/#about" scroll={false}> About </Link> </li> <li className="ml-10 text-m border-b border-transparent hover:border-current"> <Link href="/#skills" scroll={false}> Skills </Link> </li> <li className="ml-10 text-m border-b border-transparent hover:border-current"> <Link href="/#projects" scroll={false}> Projects </Link> </li> <li className="ml-10 text-m border-b border-transparent hover:border-current"> <Link href="/resume">Resume</Link> </li> <li className="ml-10 text-m border-b border-transparent hover:border-current"> <Link href="/#contact" scroll={false}> Contact </Link> </li> <li className="ml-10"> <ToggleButton /> </li> </ul> {/* Hamburger Icon */} <div className="md:hidden flex"> <ToggleButton size={25} /> <div onClick={handleNav}> <AiOutlineMenu size={25} className="ml-2" /> </div> </div> </div> </div> {/* Mobile Menu */} {/* Overlay */} <div className={ nav ? "md:hidden fixed left-0 top-0 w-full h-screen bg-black/70" : "" } > {/* Side Drawer Menu */} <div className={ nav ? "fixed left-0 top-0 w-[75%] sm:w-[60%] md:w-[45%] h-screen bg-jelly-bean-50 p-10 ease-in duration-500 dark:bg-jelly-bean-950" : "fixed left-[-100%] top-0 p-10 ease-in duration-500" } > <div> <div className="flex w-full items-center justify-between"> <Link href="/"> {mounted && ( <Image src={theme === "dark" ? DarkNavLogo : NavLogo} alt="Emre Turan - Developer Logo" width={100} height={125} className="cursor-pointer" /> )} </Link> <div onClick={handleNav} className="rounded-full shadow-lg shadow-gray-400 p-3 cursor-pointer dark:shadow-jelly-bean-900 dark:bg-jelly-bean-50 dark:text-jelly-bean-900" > <AiOutlineClose /> </div> </div> <div className="border-b border-gray-300 my-4"> <p className="w-[85%] md:w-[90%] py-4"> Let's build something together </p> </div> </div> <div className="py-4 flex flex-col"> <ul className="uppercase"> <Link href="/"> <li onClick={() => setNav(false)} className="py-4 text-sm"> Home </li> </Link> <Link href="/#about"> <li onClick={() => setNav(false)} className="py-4 text-sm"> About </li> </Link> <Link href="/#skills"> <li onClick={() => setNav(false)} className="py-4 text-sm"> Skills </li> </Link> <Link href="/#projects"> <li onClick={() => setNav(false)} className="py-4 text-sm"> Projects </li> </Link> <Link href="/resume"> <li onClick={() => setNav(false)} className="py-4 text-sm"> Resume </li> </Link> <Link href="/#contact"> <li onClick={() => setNav(false)} className="py-4 text-sm"> Contact </li> </Link> </ul> <div className="pt-40"> <p className="uppercase tracking-widest text-jelly-bean-600 dark:text-jelly-bean-200"> Let's Connect </p> <div className="flex items-center justify-between my-4 w-full sm:w-[80%]"> <a href="https://www.linkedin.com/in/emre-turan/" target="_blank" rel="noreferrer" > <div className="rounded-full shadow-lg shadow-gray-400 p-3 cursor-pointer hover:scale-105 ease-in duration-300"> <FaLinkedinIn /> </div> </a> <a href="https://github.com/etdesign" target="_blank" rel="noreferrer" > <div className="rounded-full shadow-lg shadow-gray-400 p-3 cursor-pointer hover:scale-105 ease-in duration-300"> <FaGithub /> </div> </a> <Link href="/#contact"> <div onClick={() => setNav(!nav)} className="rounded-full shadow-lg shadow-gray-400 p-3 cursor-pointer hover:scale-105 ease-in duration-300" > <AiOutlineMail /> </div> </Link> <Link href="/resume"> <div onClick={() => setNav(!nav)} className="rounded-full shadow-lg shadow-gray-400 p-3 cursor-pointer hover:scale-105 ease-in duration-300" > <BsFillPersonLinesFill /> </div> </Link> </div> </div> </div> </div> </div> </div> ); }; export default Navbar;
修改后的ToggleButton组件
// ToggleButton组件代码 import React, { useState, useEffect } from "react"; import { useTheme } from "next-themes"; import DarkModeIcon from "@mui/icons-material/DarkMode"; import LightModeIcon from "@mui/icons-material/LightMode"; const ToggleButton = () => { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { // 监听theme变化,确保主题初始化后更新UI setMounted(true); }, [theme]); // 添加theme作为依赖 return ( <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")} className="transition-all duration-100" > {mounted && (theme === "dark" ? <LightModeIcon /> : <DarkModeIcon />)} </button> ); }; export default ToggleButton;
额外优化建议
在_app.js或layout.js(Next.js 13+)中,确保ThemeProvider配置正确,保证主题初始化逻辑正常:
import { ThemeProvider } from 'next-themes' export default function App({ Component, pageProps }) { return ( <ThemeProvider attribute="class" defaultTheme="system" enableSystem={true}> <Component {...pageProps} /> </ThemeProvider> ) }
内容的提问来源于stack exchange,提问作者Emre Turan
相关产品推荐
相关产品推荐

