app router下不能直接用styled-components或emotion,因其ssr时无法捕获样式导致hydration mismatch;唯一开箱即用的是styled-jsx,而推荐方案是全局css+css模块组合。

App Router下为什么不能直接用styled-components或emotion
因为Next.js App Router默认服务端渲染(SSR),而styled-components/emotion在客户端挂载时会重新注入样式,导致hydration mismatch——服务端生成的HTML里没对应class,客户端一渲染就报错或样式闪动。你看到的“样式丢失”“控制台警告React Hydration Mismatch”,基本都源于此。
核心矛盾点:ServerStyleSheet必须在服务端捕获样式并注入HTML,但App Router的app/layout.tsx默认是服务端组件,不支持useEffect或useState等客户端钩子,也没法在服务端执行styled-components的初始化逻辑。
- 删掉
pages/_app.tsx——它在App Router中完全被忽略,留着反而干扰构建 - 不要在
app/layout.tsx里加'use client'再引入styled-components——这会让整个根布局变成客户端组件,失去SSR能力 - 别指望
createGlobalStyle能在服务端生效:它依赖客户端DOM,服务端运行时会抛ReferenceError: document is not defined
styled-jsx是App Router里唯一开箱即用的CSS-in-JS方案
Next.js原生集成styled-jsx,所有样式在构建阶段就被编译成<style></style>标签并注入HTML,服务端和客户端输出完全一致,零hydration风险。
它不依赖运行时、不需额外配置、不改Babel/Webpack,写法也最接近原生CSS:
export default function Button() {
return (
<button classname="btn">Click me</button>
<style jsx>{`
.btn {
background: #0070f3;
color: white;
padding: 8px 16px;
}
.btn:hover {
opacity: 0.8;
}
`}</style>
)
}
- 伪类(
:hover)、媒体查询(@media)、CSS变量都能直接写 - 局部作用域默认开启,类名自动哈希,不怕冲突
- 不支持运行时主题切换——想换主题得靠
:root+ JS动态改CSS变量,而不是换JS对象
硬要用styled-components?必须拆成两层组件
强行接入不是不行,但必须把样式逻辑和渲染逻辑物理隔离:服务端只负责结构,客户端才挂样式。这意味着你要写两个组件——一个纯服务端的壳,一个带'use client'的样式包装器。
例如,app/components/Button.tsx只能返回无样式的DOM结构:
export default function Button({ children }: { children: React.ReactNode }) {
return <button>{children}</button>
}
然后另建app/components/ButtonClient.tsx:
'use client'
import styled from 'styled-components'
const StyledButton = styled.button`
background: #0070f3;
color: white;
`
export default function ButtonClient({ children }: { children: React.ReactNode }) {
return <styledbutton>{children}</styledbutton>
}
- 所有
styled-components相关代码必须放在'use client'组件里,且不能出现在app/layout.tsx或app/page.tsx顶层 - 第三方库(如
react-icons)如果内部用了styled-components,也得确认它是否提供服务端安全的导出 - 性能代价明显:每个
'use client'组件都会触发客户端水合,大量使用会拖慢首屏交互时间
全局样式+CSS模块才是App Router的推荐组合
真正适合App Router的不是“选哪个CSS-in-JS”,而是放弃运行时注入思路,回归静态样式分层:全局样式管重置、工具类、基础排版;CSS模块管组件封装;需要动态逻辑时用CSS变量+className组合。
比如app/globals.css里定义:
:root {
--primary: #0070f3;
}
.btn {
background: var(--primary);
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.8;
}
再配合app/components/Button.module.css做局部覆盖:
.container {
margin: 1rem;
}
- 全局CSS必须在
app/layout.tsx首行导入,且不能加'use client' - CSS模块文件名必须含
.module.css后缀,否则会被当成全局样式处理 - 混合使用时,优先级顺序是:内联
style> CSS模块类 > 全局类——但内联style无法响应:hover或动画,慎用
复杂点在于:一旦你开始用CSS变量做主题切换,就得自己管理document.documentElement.style.setProperty的时机和范围,Next.js不会帮你做服务端同步。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











