如何在Next.js的_document.js中动态为body添加自定义类?
在Next.js中动态给body添加自定义类(解决客户端导航不更新的问题)
我之前也碰到过完全一样的困扰——用Custom Document传props给body加类根本没效果,后来在Slack里了解到服务器渲染后的body在客户端导航(next/link跳转)时不会自动更新,折腾了一阵子终于找到靠谱的解决办法,分情况给你说:
情况1:只需要在首次渲染/刷新时设置类名
如果你的类名是基于服务器端的条件(比如请求头里的用户偏好、地区设置),可以在_document.js的getInitialProps里处理,这只在服务器渲染和页面刷新时生效:
// pages/_document.js import Document, { Html, Head, Main, NextScript } from 'next/document'; class MyDocument extends Document { static async getInitialProps(ctx) { const initialProps = await Document.getInitialProps(ctx); // 这里替换成你的判断逻辑,比如从cookie取主题设置 const isDarkMode = ctx.req?.cookies?.theme === 'dark'; return { ...initialProps, isDarkMode }; } render() { const { isDarkMode } = this.props; return ( <Html> <Head /> <body className={isDarkMode ? 'dark' : ''}> <Main /> <NextScript /> </body> </Html> ); } } export default MyDocument;
划重点:这种方式不会在客户端跳转时更新body类,因为
getInitialProps只在服务器端和页面刷新时触发。
情况2:需要客户端导航时动态更新类名(比如切换深色模式)
要解决客户端跳转后body类不更新的问题,得在_app.js里监听路由变化,手动操作DOM来修改body的类名,同时可以用localStorage持久化状态:
// pages/_app.js import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; function MyApp({ Component, pageProps }) { const router = useRouter(); const [isDarkMode, setIsDarkMode] = useState(false); // 页面加载时初始化主题 useEffect(() => { const savedTheme = localStorage.getItem('theme'); const shouldBeDark = savedTheme === 'dark'; setIsDarkMode(shouldBeDark); updateBodyClass(shouldBeDark); }, []); // 监听路由切换,更新body类 useEffect(() => { const handleRouteChange = () => updateBodyClass(isDarkMode); router.events.on('routeChangeComplete', handleRouteChange); // 组件卸载时移除监听 return () => router.events.off('routeChangeComplete', handleRouteChange); }, [isDarkMode, router]); // 统一的body类更新方法 const updateBodyClass = (dark) => { if (dark) { document.body.classList.add('dark'); } else { document.body.classList.remove('dark'); } }; // 供子组件调用的主题切换方法 const toggleTheme = () => { const newMode = !isDarkMode; setIsDarkMode(newMode); localStorage.setItem('theme', newMode ? 'dark' : 'light'); updateBodyClass(newMode); }; return ( <Component {...pageProps} toggleTheme={toggleTheme} isDarkMode={isDarkMode} /> ); } export default MyApp;
如果你用的是Next.js 13+的App Router
如果已经迁移到app目录的新路由,在根布局里加客户端逻辑就能搞定:
// app/layout.js 'use client'; // 必须加这个指令才能用客户端状态和DOM操作 import { useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; export default function RootLayout({ children }) { const pathname = usePathname(); const [isDarkMode, setIsDarkMode] = useState(false); useEffect(() => { const savedTheme = localStorage.getItem('theme'); const initialDark = savedTheme === 'dark'; setIsDarkMode(initialDark); updateBodyClass(initialDark); }, []); // 路由变化时更新body类 useEffect(() => { updateBodyClass(isDarkMode); }, [pathname, isDarkMode]); const updateBodyClass = (dark) => { dark ? document.body.classList.add('dark') : document.body.classList.remove('dark'); }; const toggleTheme = () => { const newMode = !isDarkMode; setIsDarkMode(newMode); localStorage.setItem('theme', newMode ? 'dark' : 'light'); updateBodyClass(newMode); }; return ( <html lang="en"> <body> {children} {/* 可以加个切换按钮测试 */} <button onClick={toggleTheme}> {isDarkMode ? '切换浅色模式' : '切换深色模式'} </button> </body> </html> ); }
最后总结一下
- 静态类名/仅服务器端判断的场景,用
_document.js的getInitialProps就够了; - 需要动态切换或者客户端导航时更新的,必须用
_app.js(Pages Router)或客户端根布局(App Router),手动操作DOM+监听路由变化; - 用localStorage或者用户数据库存主题状态,避免刷新页面后丢失设置。
内容的提问来源于stack exchange,提问作者32423hjh32423
相关产品推荐
相关产品推荐

