在Next.js App Router中自定义Material UI主题遇SSR报错求助
解决Next.js + MUI SSR中createTheme报错问题
报错原因
Next.js 13+ App Router默认组件为服务器组件,而createTheme是Material UI的客户端专属API,无法在服务器组件的执行流程中调用,因此触发错误:
Error: Attempted to call createTheme() from the server but createTheme is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.
修复步骤
1. 修正并标记主题文件为客户端组件
修改theme.js,先修复语法错误(原赋值写法不符合ES模块规范),再标记为客户端组件:
'use client'; import { createTheme } from "@mui/material/styles"; export const theme = createTheme({ typography: { allVariants: { fontFamily: "Trebuchet MS", }, }, });
2. 抽离客户端专属布局组件
新建components/ClientLayout.js,将所有依赖MUI客户端API的UI逻辑(ThemeProvider、导航栏、AppBar等)移到这里,并标记为客户端组件:
'use client'; import { Drawer, ListItemButton, ListItemIcon, ListItemText, List, AppBar, Toolbar, Typography, } from "@mui/material"; import AssessmentIcon from "@mui/icons-material/Assessment"; import AddIcon from "@mui/icons-material/Add"; import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import zIndex from "@mui/material/styles/zIndex"; import { theme } from "../theme"; import { ThemeProvider } from "@mui/material/styles"; const navItems = [ { name: "Create New", icon: <AddIcon />, url: "todo" }, { name: "View all", icon: <FormatListBulletedIcon />, url: "todo" }, { name: "Dashboard", icon: <AssessmentIcon />, url: "todo" }, ]; const getNav = () => { return ( <List> {navItems.map((item, index) => ( <ListItemButton className="navbutton" href={item.url} key={index}> <ListItemIcon>{item.icon}</ListItemIcon> <ListItemText>{item.name}</ListItemText> </ListItemButton> ))} </List> ); }; export default function ClientLayout({ children }) { return ( <ThemeProvider theme={theme}> <AppBar position="relative" sx={{ zIndex: zIndex.drawer + 1 }}> <Toolbar> <Typography variant="h4"> --client info-- </Typography> </Toolbar> </AppBar> <Drawer anchor="left" variant="permanent"> {getNav()} </Drawer> <main>{children}</main> </ThemeProvider> ); }
3. 调整根布局为服务器组件
修改layout.js,保留它作为服务器组件(负责顶层HTML结构和metadata),并导入使用刚才创建的客户端布局:
import "./globals.css"; import ClientLayout from "./components/ClientLayout"; export const metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children }) { return ( <html lang="en"> <body> <ClientLayout>{children}</ClientLayout> </body> </html> ); }
关键说明
- RootLayout必须保持为服务器组件,因为
metadata只能在服务器组件中导出,负责页面元数据的服务器端渲染。 - 所有依赖MUI客户端API的逻辑必须放到标记了
'use client'的组件中,确保这些代码仅在客户端执行,避免服务器端调用客户端函数的冲突。 - 之前直接在RootLayout加
'use client'出现奇怪错误,是因为服务器组件专属的metadata无法在客户端组件中导出,拆分组件是最合规的解决方案。
内容的提问来源于stack exchange,提问作者cydev
相关产品推荐
相关产品推荐

