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

