在 Next.js 项目里写样式,最容易卡住的地方不是选择器怎么写,而是全局 CSS 到底应该放在哪里。框架对普通 CSS 文件的加载入口有硬性限制,如果直接在业务组件中 import 一个 .css 文件,构建阶段就会报错,提示全局样式只能从 _app.tsx 或根布局文件导入。这个设计推动我们把样式拆成清晰的层次:全局部分负责 reset、字体、主题变量和基础排版,组件部分则用 CSS Modules 实现局部作用域。两者配合起来,项目再大也不会轻易出现类名冲突。

一、全局样式需要放在唯一入口
在 Pages Router 中,全局样式文件通常命名为 globals.css,并放在 styles 目录下。入口是 pages/_app.tsx,这个文件包裹所有页面组件,因此在这里 import 的样式会注入到每个路由。示例代码如下:
import '../styles/globals.css';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
如果项目已经迁移到 App Router,则全局样式应该在 app/layout.tsx 中导入。根布局负责整个 HTML 文档的骨架,在这里加载 globals.css 同样能覆盖所有路由。需要注意的是,一个 Next.js 项目里不能在不同布局文件中重复导入同一个全局 CSS 文件,否则会触发重复注入错误。
import './globals.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body>{children}</body>
</html>
);
}
从职责划分来看,全局样式文件里最适合放几类内容:CSS Reset 或 Normalize、body 的字体与背景、标题标签的基础字号、颜色变量、通用工具类。比如项目定好的主色、圆角、间距都可以挂到 :root 下,后续所有模块样式通过 var 函数取值。这样一来,主题调整只发生在 globals.css 一个文件里,组件不需要跟着改。
还有一种常见的处理方式是引入第三方的 reset 库。只要在全局入口 import 它的 CSS 文件即可,但如果库提供了普通 CSS,同样只能在这个入口加载,不能分散到组件中。
二、CSS Modules 负责组件级隔离
组件内部的样式最好使用 CSS Modules。Next.js 对以 .module.css 结尾的文件会自动启用模块化处理,构建时把 .button 这类类名重命名成类似 Button_button__xxxx 的哈希字符串,并生成一个映射对象。组件 import 进来的不是一个字符串,而是一个样式对象,因此多个组件都定义 .button 也不会互相污染。
/* components/Button.module.css */
.button {
background: #1864ab;
color: #fff;
padding: 10px 18px;
border-radius: 6px;
border: 0;
cursor: pointer;
}
.primary {
background: #2f9e44;
}
import styles from './Button.module.css';
export default function Button({ primary }) {
return (
<button className={primary ? `${styles.button} ${styles.primary}` : styles.button}>
提交
</button>
);
}
这种隔离来自构建层的类名哈希,而不是运行时作用域,所以性能开销可以忽略。类名带连字符时要通过方括号访问,例如 styles['card-title']。动态类名也不能直接拼接原始字符串,因为模块文件里的 card-title 在最终 DOM 中已经变成了哈希名,拼接 styles[`card-title-${type}`] 通常拿不到值。更稳妥的做法是事先定义映射对象,再根据状态取值。
const typeClassMap = {
primary: styles.primary,
danger: styles.danger,
};
<button className={`${styles.button} ${typeClassMap[type] || ''}`}>删除</button>
CSS Modules 也支持组合规则。如果希望一个类复用另一个类的声明,可以在同一模块文件里使用 composes。它比 Sass 的 @extend 更轻量,也不会把无关选择器扩散到全局。
.base {
padding: 8px 14px;
border-radius: 6px;
}
.danger {
composes: base;
background: #c92a2a;
color: #fff;
}
当某个第三方编辑器或富文本组件需要接收全局类名,而你又不想把这些样式写到 globals.css 时,可以在模块文件里用 :global 包裹选择器。例如 .richText :global(.ProseMirror) 会保留 ProseMirror 的原始类名,同时让样式只在这个组件的 .richText 容器内生效。
三、用全局变量把两套体系串联起来
全局样式和 CSS Modules 并不是非此即彼的关系。推荐的做法是:globals.css 只定义基础层和变量层,具体组件样式全部放进 .module.css 文件。这样全局文件不会越写越长,模块文件也不会硬编码颜色和圆角值。
/* styles/globals.css */
:root {
--brand-color: #0b7285;
--text-main: #212529;
--radius-md: 8px;
}
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
color: var(--text-main);
}
组件模块直接使用这些变量:
/* components/Card.module.css */
.card {
border: 1px solid #dee2e6;
border-radius: var(--radius-md);
padding: 16px;
transition: border-color 0.2s;
}
.card:hover {
border-color: var(--brand-color);
}
这样做有两个明显好处。第一,主题升级时只需修改 :root 里的变量,所有组件的视觉风格会同步更新。第二,模块文件依然保持局部作用域,.card 只属于 Card 组件,不会影响其他模块。相比把 .card 写成全局类名,这种组合方式对大型项目更友好。
如果团队使用 Sass,还可以用 SCSS 变量和 mixin 来组织设计令牌。Next.js 内置 Sass 支持,安装 sass 后直接把文件后缀改成 .module.scss 即可。共享变量放在 _variables.scss 中,组件模块通过 @use 引入,不会产生重复样式输出。
// styles/_variables.scss
$brand: #0b7285;
$radius: 8px;
// components/Panel.module.scss
@use '../styles/variables' as *;
.panel {
border-radius: $radius;
border-left: 4px solid $brand;
}
四、容易踩中的几个坑
第一个坑是全局 CSS 导入位置错误。只要在非入口文件里 import 普通 CSS,Next.js 就会中止构建。解决方法不是关闭限制,而是把基础样式提升到 _app.tsx 或根布局,把组件样式改成 CSS Modules。
第二个坑是类名访问方式。CSS 类名建议使用驼峰或短横线命名,但一旦使用短横线,就必须用方括号读取。如果大量出现 styles['card-title'] 这种写法,说明命名可以再调整,保持与 JavaScript 风格一致会更顺手。
第三个坑是覆盖第三方样式。以 antd 为例,如果想调整某个组件的内部背景,用 .module.css 的局部类名是覆盖不到的,因为第三方组件的类名不是哈希名。正确做法是在全局样式里针对稳定的类名覆盖,或者用容器类加 :global 限定影响范围,避免污染其他页面。
第四个坑是 CSS 和 JS 的状态类冲突。例如按钮激活态既有 props 控制,又有 hover 和 focus。建议把交互状态尽量交给 CSS 的伪类处理,只有业务状态才映射到额外的局部类名。这样能减少动态拼接,也能让样式逻辑更集中。
总体上,Next.js 的样式体系并不复杂,关键是把全局层和组件层分清楚。全局层负责基础、变量和少量跨组件工具类,组件层用 CSS Modules 封装实现。两者通过 CSS 变量或 Sass 变量连接,既能维持全局一致性,又不会牺牲组件间的隔离性。项目规模越大,这种结构的收益就越明显。
Next.jsCSS Modules全局样式修改时间:2026-09-18 07:11:31