Material UI 与 Next.js App Router 的 TypeScript 示例工程全解析:从项目结构到主题定制
Material UI 官方在仓库 examples/material-ui-nextjs-ts 中维护了一个面向生产实践的 TypeScript 示例工程,它演示了在 Next.js(App Router)下集成 Material UI 的完整做法:服务端渲染下的 Emotion 样式缓存、基于 CSS 变量的主题与明暗模式切换、以及如何在 TSX 中组合布局组件与页面路由。读完本文,你将掌握一套可以直接复刻到真实项目中的「Next.js + Material UI + TypeScript」初始化模板,并理解每一处关键配置背后的源码原理。
一、示例工程概述与适用场景
该示例是一个由 create-next-app 引导、随后安装并接入 Material UI 的完整 Next.js 项目,采用 App Router 目录约定与 TypeScript 类型系统。从 package.json 可以看到其依赖选型:
next:^16.0.7;react/react-dom:^19.0.0;@mui/material、@mui/icons-material:最新版;@mui/material-nextjs:官方为 Next.js 提供的集成包(App Router 缓存与样式注入);@emotion/cache、@emotion/react、@emotion/styled:Material UI 默认样式引擎所需;- 开发依赖包含
typescript、@types/react等与 ESLint 配置。
工程目录非常精简,核心源码集中在 src/ 之下:
examples/material-ui-nextjs-ts/
├── src/
│ ├── app/ # App Router 路由
│ │ ├── about/page.tsx # /about 页面
│ │ ├── layout.tsx # 根布局:主题 + 样式缓存
│ │ └── page.tsx # / 首页
│ ├── components/ # 示例组件
│ │ ├── Copyright.tsx
│ │ ├── Link.tsx # 桥接 MUI Link 与 Next Link
│ │ ├── ModeSwitch.tsx # 明暗模式切换
│ │ └── ProTip.tsx
│ └── theme.ts # createTheme 主题定义
├── next.config.mjs
└── tsconfig.json
这一结构是 Material UI 官方推荐的起点形态:根布局只做"基础设施"(缓存、主题、基线样式),页面与组件各自独立,主题集中管理,便于后续横向扩展。
二、快速启动:安装、运行与在线体验
按官方文档说明,可以直接从仓库提取该目录并运行:
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/material-ui-nextjs-ts
cd material-ui-nextjs-ts
然后安装依赖并启动开发服务器:
npm install
npm run dev
用浏览器打开 http://localhost:3000 即可看到首页;package.json 中还提供了生产级脚本:npm run build 执行 next build,npm run start 执行 next start。
如果你不想在本地搭建,官方 README 同时提供了两种云端沙箱入口(StackBlitz 与 CodeSandbox),可直接基于 examples/material-ui-nextjs-ts 目录在线打开预览。此外,src/app/about/page.tsx 演示了第二个路由 /about,它与首页之间通过 Material UI 按钮互相导航,可用于验证多页面的样式渲染与客户端路由是否正常。
三、根布局:集成 Material UI 的关键一环
App Router 下所有页面都挂在 src/app/layout.tsx 中。它按顺序完成了四件事,这也是任何 Next.js 项目中接入 Material UI 的标准步骤:
import { AppRouterCacheProvider } from '@mui/material-nextjs/v16-appRouter';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
import theme from '@/theme';
import ModeSwitch from '@/components/ModeSwitch';
export default function RootLayout(props: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<InitColorSchemeScript attribute="class" />
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
<ThemeProvider theme={theme}>
<CssBaseline />
<ModeSwitch />
{props.children}
</ThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}
3.1 AppRouterCacheProvider:解决 SSR 样式注入问题
AppRouterCacheProvider 来自 @mui/material-nextjs 包(对应 App Router 的入口是 v16-appRouter)。其作用是创建一个 Emotion 缓存(cache)实例,并把所有子组件的 Emotion 样式统一写入该缓存,最终在服务端随 HTML 一起输出,从而避免样式闪烁(FOUC),保证服务端渲染结果与客户端一致。
从源码看,该 provider 还接受 options 参数,其中 enableCssLayer 用于开启 CSS @layer 样式分层:
- 在 v13-appRouter/appRouterV13.tsx 中,
options.enableCssLayer被声明为可选布尔值,并在注入样式时通过正则match(/^@layer\s+[^{]*$/)等逻辑判断如何处理样式内容; - 开启后,组件库样式被收纳进层叠上下文(如
@layer),有助于规避第三方全局样式与组件样式之间的优先级冲突。
示例中设为 enableCssLayer: true,正是为了在真实项目中(常伴随 Tailwind、全局 CSS)让 Material UI 样式层级更可控。
3.2 ThemeProvider + CssBaseline + InitColorSchemeScript
ThemeProvider theme={theme}:把 src/theme.ts 中createTheme的结果注入组件树;CssBaseline:提供一套统一、简洁的全局基线样式(重置 margin、统一字体渲染、设置背景色等);InitColorSchemeScript:在首屏 HTML 的<head>之前注入一段脚本(attribute="class"),用于在浏览器解析样式前读取本地存储或系统偏好,提前在<html>上设置class,避免暗色主题首次渲染时闪烁。它要求suppressHydrationWarning配合使用,因此示例在<html>上加了该属性。
四、主题文件:CSS 变量、色彩方案与字体加载
src/theme.ts 是整个示例的样式中心,声明了 'use client'(因为 next/font 的样式对象需要在客户端可用)。它演示了三个值得关注的现代 Material UI 能力:
'use client';
import { createTheme } from '@mui/material/styles';
import { Roboto } from 'next/font/google';
const roboto = Roboto({
weight: ['300', '400', '500', '700'],
subsets: ['latin'],
display: 'swap',
});
const theme = createTheme({
colorSchemes: { light: true, dark: true },
cssVariables: {
colorSchemeSelector: 'class',
},
typography: {
fontFamily: roboto.style.fontFamily,
},
components: {
MuiAlert: {
styleOverrides: {
root: {
variants: [
{
props: { severity: 'info' },
style: { backgroundColor: '#60a5fa' },
},
],
},
},
},
},
});
export default theme;
各部分含义如下:
colorSchemes: { light: true, dark: true }:启用 Material UI v6+ 的多色彩方案支持。相比旧版palette.mode的手动切换,这里让主题同时声明明暗两套方案,供运行时的模式切换机制消费。cssVariables.colorSchemeSelector: 'class':要求把生成的 CSS 变量以class选择器挂载(对应根布局中InitColorSchemeScript attribute="class"),将当前模式反映为<html>上的 CSS class,再配合 CSS 变量实现全局换肤。typography.fontFamily:使用next/font/google加载 Roboto,并把字体样式接入主题。这样做的好处是字体由 Next.js 自托管、自动display: swap,无需手工在 HTML 中引<link>。components.MuiAlert.styleOverrides:示例性地展示了对组件内部样式的覆写——通过root.variants为severity="info"的 Alert 单独指定背景色,说明可以在主题层声明"组件级变体规则"。
五、暗色模式切换的组件级实现
ModeSwitch.tsx 是整套明暗切换的交互端:
'use client';
import { useColorScheme } from '@mui/material/styles';
// ...
export default function ModeSwitch() {
const { mode, setMode } = useColorScheme();
if (!mode) {
return null; // 首次渲染时 mode 尚未确定,先不渲染以免不一致
}
return (
// ...
<Select
value={mode}
onChange={(event) => setMode(event.target.value as typeof mode)}
label="Theme"
>
<MenuItem value="system">System</MenuItem>
<MenuItem value="light">Light</MenuItem>
<MenuItem value="dark">Dark</MenuItem>
</Select>
);
}
它通过 useColorScheme() 拿到当前 mode 与 setMode,用 Material UI 的 Select 提供 system / light / dark 三个选项。其中 system 会跟随操作系统偏好,而切换结果最终通过 CSS class 作用于 <html>,与 layout.tsx 中的 InitColorSchemeScript、theme.ts 中的 colorSchemeSelector: 'class' 三者闭环。组件在 mode 尚未就绪时返回 null,避免首屏水合差异。
六、MUI 路由链接与 Next.js Link 的桥接模式
Next.js 要求站内跳转使用 next/link,而 Material UI 的 <Link> 组件负责渲染样式。示例在 components/Link.tsx 中给出了官方推荐的桥接写法:
'use client';
import Link from 'next/link';
export default Link;
这个文件把 next/link 原样导出。然后在页面中通过 Material UI 的 component 注入机制让 MUI Link 渲染成 Next Link:
- 首页 page.tsx:
<MaterialUILink component={NextLink} href="/about" color="secondary">; - 关于页 about/page.tsx:
<Button variant="contained" component={Link} href="/">。
这样既保留 MUI 主题下的颜色、排版与涟漪效果,又获得 Next.js 的客户端路由预取与导航能力。此外两个页面还共享 ProTip.tsx(提示文案 + 自定义 SvgIcon)与 Copyright.tsx(页脚版权),后者演示了 Typography + MUI Link 组合典型页脚。
七、周边配置速览
- next.config.mjs:当前为空配置对象,说明本项目无需额外 webpack/next 插件即可运行,Material UI 的样式方案完全由根布局中的
AppRouterCacheProvider承载; - tsconfig.json:开启
strict,启用jsx: "react-jsx",并配置paths: { "@/*": ["./src/*"] }——这也是源码里import theme from '@/theme'、@/components/...能生效的原因;include额外收录了.next/types/**/*.ts以便 Next.js 生成的类型参与编译。
八、进一步学习方向
官方为这类示例给出的延伸阅读路径包括:Next.js 的 App Router 文档(说明该工程基于 create-next-app 引导,可直接参考 Next.js 官方 docs 学习其特性与 API)与 Material UI 的定制指南(从主题定制出发,了解 createTheme、组件覆写、colorSchemes 等能力的完整参数)。在本仓库中还可以继续对比:
- 纯 JS 版本的姊妹工程 examples/material-ui-nextjs,观察引入 TypeScript 前后在目录与类型声明上的差异;
- App Router 集成包的源码 packages/mui-material-nextjs,了解
AppRouterCacheProvider的缓存与样式注入细节; - Material UI 主题与样式系统的更多示例,可参考 docs/src/modules 中围绕主题、暗色模式封装的基础设施实现。
总而言之,这个示例工程的价值在于:它把"SSR 缓存、CSS 变量主题、明暗模式、字体、路由桥接、多页面"这些最容易踩坑的集成点浓缩在一个最小但可运行的 TypeScript 项目中。把它跑通后,再结合 src/theme.ts 中的主题结构逐步扩展自己的设计体系,即可平滑起步正式的 Material UI + Next.js 应用开发。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00