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

Next.js初始渲染用localStorage存主题遇服务端报错的解决方法

解决Next.js SSR下主题切换无闪烁且避免localStorage未定义错误的方案

方案一:根布局/文档注入初始化脚本(推荐)

在Next.js的根布局(App Router)或_document.js(Pages Router)中添加内联脚本,在页面DOM加载前就读取localStorage并设置主题类,确保首次渲染直接应用正确主题,避免闪烁。

App Router(app/layout.js)

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN">
      <head>
        {/* 可添加其他head内容 */}
      </head>
      <body>
        {/* 内联脚本,优先于页面内容执行 */}
        <script dangerouslySetInnerHTML={{
          __html: `
            const savedTheme = localStorage.getItem('theme') || 'light';
            document.documentElement.classList.toggle('dark', savedTheme === 'dark');
          `
        }} />
        {children}
      </body>
    </html>
  );
}

Pages Router(pages/_document.js)

import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html lang="zh-CN">
        <Head />
        <body>
          <script dangerouslySetInnerHTML={{
            __html: `
              const savedTheme = localStorage.getItem('theme') || 'light';
              document.documentElement.classList.toggle('dark', savedTheme === 'dark');
            `
          }} />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

对应的useDarkSide Hook

'use client';

import { useState } from 'react';

export default function useDarkSide() {
  // 仅在客户端环境读取localStorage,SSR时返回默认值
  const [theme, setTheme] = useState(() => {
    if (typeof window !== 'undefined') {
      return localStorage.getItem('theme') || 'light';
    }
    return 'light';
  });

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.classList.toggle('dark', newTheme === 'dark');
  };

  return { theme, toggleTheme };
}

方案二:客户端组件顶层直接初始化主题

在use client组件的顶层代码中,先判断浏览器环境并设置主题类,确保组件渲染前主题已生效:

'use client';

import { useState } from 'react';

// 组件加载时立即初始化主题,仅在客户端执行
if (typeof window !== 'undefined') {
  const savedTheme = localStorage.getItem('theme') || 'light';
  document.documentElement.classList.toggle('dark', savedTheme === 'dark');
}

export default function useDarkSide() {
  const [theme, setTheme] = useState(() => {
    if (typeof window !== 'undefined') {
      return localStorage.getItem('theme') || 'light';
    }
    return 'light';
  });

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.classList.toggle('dark', newTheme === 'dark');
  };

  return { theme, toggleTheme };
}

原理说明

  • 两种方案均通过在客户端DOM渲染前执行主题设置,避免了useEffect异步执行导致的闪烁问题。
  • SSR阶段不会访问localStorage,自然不会抛出ReferenceError。
  • 客户端初始化时,useState的初始化函数会读取正确的主题值,保证组件状态与DOM类名同步。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:06:00