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

Next.js中useDarkMode钩子实现的暗黑模式刷新后不持久化问题

问题

在Next.js应用中使用use-dark-mode钩子和@nextui-org/react组件开发主题切换组件,暗黑模式切换功能正常,但刷新页面后总会重置为浅色模式,即便之前已选择暗黑模式。

相关代码

import React from 'react';
import { Switch, Button } from '@nextui-org/react';
import { IoIosMoon } from 'react-icons/io';
import { FaSun } from 'react-icons/fa';
import useDarkMode from 'use-dark-mode';

interface ThemeToggleProps {
  isCompact: boolean;
  className?: string; // Optional className prop
}

export const ThemeToggle: React.FC<ThemeToggleProps> = ({
  isCompact,
  className,
}) => {
  const darkMode = useDarkMode(false); // Default to light mode

  return (
    <div className={className}>
      {isCompact ? (
        <Button
          isIconOnly
          onClick={darkMode.toggle} // Toggle between light and dark mode
          variant="light"
        >
          {darkMode.value ? <IoIosMoon size={24} /> : <FaSun size={24} />}
        </Button>
      ) : (
        <Switch
          isSelected={darkMode.value} // Check if dark mode is enabled
          onChange={darkMode.toggle} // Toggle between light and dark mode
          size="lg"
          color="secondary"
          startContent={<IoIosMoon />} // Icon for dark mode
          endContent={<FaSun />} // Icon for light mode
        />
      )}
    </div>
  );
};

现象

  • 切换暗黑模式后,主题变更正常
  • 刷新页面后,总会默认回到浅色模式
  • use-dark-mode钩子似乎无法跨页面刷新持久化主题设置

已尝试

  • 原本预期use-dark-mode会将状态存储在localStorage的默认dark-mode键下,但未成功持久化
  • 怀疑与Next.js的SSR有关,或是初始渲染时忽略了localStorage的值

请问如何确保暗黑模式偏好跨页面刷新持久化?此问题是否因use-dark-mode不兼容Next.js SSR导致?最佳修复方案是什么?


解决方案

问题根源

确实是Next.js的SSR机制导致的问题:

  • 服务端渲染时,localStorage不存在,use-dark-mode无法读取存储的偏好设置,只能使用传入的默认值false(浅色模式)
  • 客户端 hydration 阶段,组件初始渲染仍沿用服务端的默认值,之后才会读取localStorage,但此时可能已触发主题重置,或NextUI主题未同步更新

use-dark-mode本身支持localStorage持久化,但在SSR场景下需要特殊处理初始值的获取逻辑。

修复步骤

1. 适配SSR的use-dark-mode初始值调整

不要直接传固定的false作为默认值,通过环境判断在客户端优先读取localStorage,服务端保持默认:

const darkMode = useDarkMode(
  typeof window !== 'undefined' ? localStorage.getItem('dark-mode') === 'true' : : false
);

2. 用next-themes配合NextUI官方方案(推荐)

next-themes专门适配Next.js的SSR/SSG场景,能自动处理持久化、主题同步和hydration匹配问题,步骤如下:

  • 安装依赖:
npm install next-themes
  • 在_app.tsx(App Router用layout.tsx)中配置主题提供者:
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { NextUIProvider } from '@nextui-org/react';

export default function App({ Component, pageProps }) {
  return (
    <NextThemesProvider defaultTheme="light" attribute="class">
      <NextUIProvider>
        <Component {...pageProps} />
      </NextUIProvider>
    </NextThemesProvider>
  );
}
  • 修改ThemeToggle组件使用useTheme钩子:
import React from 'react';
import { Switch, Button } from '@nextui-org/react';
import { IoIosMoon } from 'react-icons/io';
import { FaSun } from 'react-icons/fa';
import { useTheme } from 'next-themes';

interface ThemeToggleProps {
  isCompact: boolean;
  className?: string;
}

export const ThemeToggle: React.FC<ThemeToggleProps> = ({
  isCompact,
  className,
}) => {
  const { theme, setTheme } = useTheme();

  const toggleTheme = () => {
    setTheme(theme === 'dark' ? 'light' : 'dark');
  };

  return (
    <div className={className}>
      {isCompact ? (
        <Button
          isIconOnly
          onClick={toggleTheme}
          variant="light"
        >
          {theme === 'dark' ? <IoIosMoon size={24} /> : <FaSun size={24} />}
        </Button>
      ) : (
        <Switch
          isSelected={theme === 'dark'}
          onChange={toggleTheme}
          size="lg"
          color="secondary"
          startContent={<IoIosMoon />}
          endContent={<FaSun />}
        />
      )}
    </div>
  );
};

3. 挂载后同步状态(兼容原有use-dark-mode)

如果坚持使用use-dark-mode,在组件挂载后手动同步localStorage状态,避免hydration不匹配:

import { useEffect } from 'react';

export const ThemeToggle: React.FC<ThemeToggleProps> = ({
  isCompact,
  className,
}) => {
  const darkMode = useDarkMode(false);

  useEffect(() => {
    const savedMode = localStorage.getItem('dark-mode') === 'true';
    if (savedMode !== darkMode.value) {
      darkMode.set(savedMode);
    }
  }, []);

  // 其余代码不变
};

最佳方案

优先选择next-themes配合NextUI官方主题方案,它完全适配Next.js的服务端渲染逻辑,无需额外处理环境判断,代码更简洁且可靠性更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 07:58:16