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

NextJS中如何在Hydration前基于localStorage修改body样式?

解决Next.js中主题切换的页面渲染延迟问题

在Next.js应用中实现主题持久化(将主题选择存在localStorage)时,刷新页面后主题样式总是在页面渲染后才生效,存在明显延迟。以下是问题分析、已尝试方案及最佳解决办法:

已尝试的方案

方案1:客户端Context + useEffect

通过客户端组件的Context管理主题状态,用useEffect读取localStorage并设置body类:

"use client"
import { createContext, useEffect, useState } from "react"

export const DarkThemeContext = createContext()

const DarkThemeProvider = ({ children }) => {
  const [darkTheme, setDarkTheme] = useState(false)

  useEffect(() => {
    if (localStorage.getItem("darkTheme")) setDarkTheme(true)
  }, [])

  useEffect(() => {
    if (darkTheme) {
      document.body.classList.add("dark-theme")
      localStorage.setItem("darkTheme", JSON.stringify(true))
    } else {
      document.body.classList.remove("dark-theme")
      localStorage.removeItem("darkTheme")
    }
  }, [darkTheme])

  return (
    <DarkThemeContext.Provider value={{ darkTheme, setDarkTheme }}>
      {children}
    </DarkThemeContext.Provider>
  )
}

export default DarkThemeProvider

将Provider导入RootLayout后,因useEffect在组件挂载后执行,页面已渲染部分内容,导致延迟。

方案2:使用next/script

在RootLayout中添加next/script执行主题检测:

import Script from "next/script"
import Header from "./_components/Header/Header"
import DarkThemeProvider from "./_context/DarkThemeProvider"
import "./_styles/_globals.scss"

export const metadata = {
  title: "NextJS",
  description: "Generated by Next.js",
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Script id="theme-script">
          {`if (localStorage.getItem("darkTheme")) {
          document.body.classList.add("dark-theme")
          localStorage.setItem("darkTheme", JSON.stringify(true))
        } else {
          document.body.classList.remove("dark-theme")
          localStorage.removeItem("darkTheme")
        }`}
        </Script>
        <DarkThemeProvider>
          <Header />
          <main className="main">{children}</main>
        </DarkThemeProvider>
      </body>
    </html>
  )
}

但next/script默认执行时机较晚,仍无法避免渲染延迟。

最佳解决办法

核心思路:在DOM渲染前执行主题检测脚本

将主题检测脚本放在RootLayout的<head>中,直接操作<html>标签(而非<body>,因为<body>此时尚未被解析),脚本会在页面内容渲染前立即执行,完全消除延迟。

1. 修改RootLayout

import Header from "./_components/Header/Header"
import DarkThemeProvider from "./_context/DarkThemeProvider"
import "./_styles/_globals.scss"

export const metadata = {
  title: "NextJS",
  description: "Generated by Next.js",
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        {/* 立即执行的主题检测脚本,在DOM渲染前运行 */}
        <script dangerouslySetInnerHTML={{
          __html: `
            if (localStorage.getItem("darkTheme")) {
              document.documentElement.classList.add("dark-theme");
            } else {
              document.documentElement.classList.remove("dark-theme");
            }
          `
        }} />
      </head>
      <body>
        <DarkThemeProvider>
          <Header />
          <main className="main">{children}</main>
        </DarkThemeProvider>
      </body>
    </html>
  )
}

2. 同步DarkThemeProvider状态

调整Provider,初始化时直接读取localStorage,确保状态与脚本设置的类一致,并支持多标签页同步:

"use client"
import { createContext, useEffect, useState } from "react"

export const DarkThemeContext = createContext()

const DarkThemeProvider = ({ children }) => {
  // 初始化时优先读取localStorage,无值则用系统主题偏好
  const [darkTheme, setDarkTheme] = useState(() => {
    if (typeof window !== "undefined") {
      const saved = localStorage.getItem("darkTheme")
      if (saved !== null) {
        return saved === "true"
      }
      // 可选:结合系统深色模式偏好
      return window.matchMedia("(prefers-color-scheme: dark)").matches
    }
    return false
  })

  useEffect(() => {
    // 同步主题类到html标签
    const syncThemeClass = () => {
      if (darkTheme) {
        document.documentElement.classList.add("dark-theme")
        localStorage.setItem("darkTheme", "true")
      } else {
        document.documentElement.classList.remove("dark-theme")
        localStorage.removeItem("darkTheme")
      }
    }

    syncThemeClass()

    // 监听localStorage变化,实现多标签页主题同步
    const handleStorageChange = (e) => {
      if (e.key === "darkTheme") {
        setDarkTheme(!!e.newValue)
      }
    }
    window.addEventListener("storage", handleStorageChange)

    return () => window.removeEventListener("storage", handleStorageChange)
  }, [darkTheme])

  return (
    <DarkThemeContext.Provider value={{ darkTheme, setDarkTheme }}>
      {children}
    </DarkThemeContext.Provider>
  )
}

export default DarkThemeProvider

3. 调整全局样式

将主题样式基于<html>标签编写,示例:

// 浅色主题默认样式
body {
  background-color: #fff;
  color: #333;
}

// 深色主题样式
html.dark-theme body {
  background-color: #1a1a1a;
  color: #fff;
}

为什么这个方案有效?

  • <head>中的内联脚本会在浏览器解析HTML时立即执行,此时<html>标签已存在,能直接添加类,完全早于页面内容渲染。
  • 初始化状态时直接读取localStorage,避免了组件挂载后再更新状态的延迟。
  • 监听storage事件,实现多标签页之间的主题同步,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:27:40