本系列文章将围绕Next.js技术栈,旨在为AI Agent开发者提供一套完整的客户端侧工程实践指南。
优秀的用户界面源于精心组织的代码结构。Next.js 支持多种样式方案,每种方案都有其特定的适用场景。选择合适的工具将使样式开发过程更加高效和愉悦。
一、样式方案概览
Next.js 对样式方案没有任何强制限制,几乎所有主流的 CSS 解决方案都能开箱即用。主要选项包括:
mindmap
root((Next.js 样式方案))
Tailwind CSS
工具类优先
高度可定制
开发效率高
CSS Modules
局部作用域
原生 CSS
无额外依赖
全局样式
app/globals.css
适合全局变量
[CSS-in-JS]
Styled Components
Emotion
需特殊配置
Sass / SCSS
变量和嵌套
向后兼容
选择建议
根据项目需求选择合适的方案:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 新项目启动 | Tailwind CSS | 开发速度最快,生态完善 |
| 需要高度隔离的组件 | CSS Modules | 自动作用域隔离,无命名冲突 |
| 已有 Sass 技术栈 | Sass/SCSS | 保持团队熟悉度,平滑迁移 |
| 需要动态控制样式 | CSS-in-JS | 注意服务端渲染配置成本 |
二、Tailwind CSS:实用优先的现代化方案
Tailwind CSS 是一个 "实用优先"(utility-first) 的 CSS 框架。不同于 Bootstrap 提供预构建组件,Tailwind 提供数百个原子级工具类,通过组合类名构建自定义 UI。
使用 create-next-app 创建项目时选择 Tailwind CSS 选项,系统将自动完成所有配置。
1. 基础用法
// 无需编写额外 CSS 文件,直接在 JSX 中使用工具类
export function Button({
children,
variant = 'primary'
}: {
children: React.ReactNode
variant?: 'primary' | 'secondary' | 'danger'
}) {
const variants = {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-800',
danger: 'bg-red-600 hover:bg-red-700 text-white',
};
return (
<button
className={`
${variants[variant]}
px-4 py-2 rounded-lg font-medium
transition-colors duration-200
focus:outline-none focus:ring-2 focus:ring-offset-2
disabled:opacity-50 disabled:cursor-not-allowed
`}
>
{children}
</button>
);
}
2. 响应式设计
Tailwind 提供断点前缀实现响应式布局:
export function ProductCard({ product }: { product: Product }) {
return (
<div className="
flex flex-col /* 移动端:垂直排列 */
md:flex-row /* 平板及以上:水平排列 */
gap-4 p-4
border rounded-xl
hover:shadow-lg
transition-shadow
">
<img
src={product.image}
className="w-full md:w-48 h-48 object-cover rounded-lg"
alt={product.name}
/>
<div className="flex flex-col justify-between">
<div>
<h3 className="text-xl font-bold">{product.name}</h3>
<p className="text-gray-500 mt-1">{product.description}</p>
</div>
<span className="text-2xl font-bold text-blue-600 mt-4">
¥{product.price}
</span>
</div>
</div>
);
}
常用断点前缀(可配置):
sm:- 640px 及以上md:- 768px 及以上lg:- 1024px 及以上xl:- 1280px 及以上2xl:- 1536px 及以上
3. 暗色模式支持
Tailwind 内置暗色模式支持:
// tailwind.config.ts
const config = {
darkMode: 'class', // 通过 .dark 类名控制暗色模式
// ...
};
// 使用 dark: 前缀定义暗色模式样式
export function Card({ children }: { children: React.ReactNode }) {
return (
<div className="
bg-white dark:bg-gray-800
text-gray-900 dark:text-gray-100
border border-gray-200 dark:border-gray-700
rounded-lg p-6 shadow-sm
">
{children}
</div>
);
}
// 实现暗色模式切换功能
'use client';
import { useState, useEffect } from 'react';
export function ThemeToggle() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
// 读取系统偏好设置
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setIsDark(prefersDark);
document.documentElement.classList.toggle('dark', prefersDark);
}, []);
const toggleTheme = () => {
setIsDark(!isDark);
document.documentElement.classList.toggle('dark');
};
return (
<button
onClick={toggleTheme}
className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
aria-label={isDark ? '切换到亮色模式' : '切换到暗色模式'}
>
{isDark ? '🌙' : '☀️'}
</button>
);
}
4. 自定义设计系统
Tailwind 的真正优势在于其强大的定制能力。可在 tailwind.config.ts 中扩展或覆盖默认主题,建立符合品牌的设计系统:
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./app/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}'
],
theme: {
extend: {
// 自定义颜色系统
colors: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
surface: {
DEFAULT: '#ffffff',
dark: '#0f172a',
},
},
// 自定义字体家族
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
// 自定义动画
keyframes: {
'slide-in': {
from: { transform: 'translateX(-100%)' },
to: { transform: 'translateX(0)' },
},
},
animation: {
'slide-in': 'slide-in 0.3s ease-out',
},
},
},
plugins: [],
};
export default config;
5. 类名管理最佳实践
当条件类名增多时,组件代码会变得难以阅读。推荐使用 clsx + tailwind-merge 组合:
npm install clsx tailwind-merge
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
/**
* 合并类名并处理 Tailwind 冲突
* 这是 Next.js 项目中最常见的工具函数之一
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { cn } from '@/lib/utils';
interface BadgeProps {
variant: 'success' | 'warning' | 'error' | 'info';
children: React.ReactNode;
className?: string;
}
export function Badge({ variant, children, className }: BadgeProps) {
return (
<span
className={cn(
// 基础样式
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
// 根据 variant 动态添加样式
variant === 'success' && 'bg-green-100 text-green-800',
variant === 'warning' && 'bg-yellow-100 text-yellow-800',
variant === 'error' && 'bg-red-100 text-red-800',
variant === 'info' && 'bg-blue-100 text-blue-800',
// 外部传入的 className 优先级最高(twMerge 会处理冲突)
className
)}
>
{children}
</span>
);
}
twMerge 的核心价值:智能处理 Tailwind 类名冲突。例如 cn('px-4', 'px-6') 将正确输出 px-6,而非同时保留两者(后者会导致 px-4 无法被覆盖)。
三、CSS Modules:经典可靠的隔离方案
CSS Modules 是 Next.js 内置支持的样式方案,无需额外配置。其核心特性是自动作用域隔离——类名会被自动添加哈希后缀,避免与其他文件的类名冲突。
1. 基本用法
通过创建xxx.module.css文件实现。
/* components/Button.module.css */
.button {
padding: 8px 16px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.primary {
background-color: #2563eb;
color: white;
}
.primary:hover {
background-color: #1d4ed8;
}
/* 响应式媒体查询 */
@media (max-width: 768px) {
.button {
width: 100%;
}
}
// components/Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary';
}
export function Button({
children,
variant = 'primary'
}: ButtonProps) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
构建后,类名将转换为类似 Button_button__a3X9z 的哈希名称,实现完全隔离。
2. 与全局类名组合
有时需要混合使用全局类名(如 Tailwind 工具类)和 CSS Modules:
import styles from './Card.module.css';
export function Card({ children }: { children: React.ReactNode }) {
return (
// 组合 CSS Module 类名和全局类名
<div className={`${styles.card} shadow-lg rounded-xl`}>
{children}
</div>
);
}
3. 适用场景
CSS Modules 特别适合以下场景:
- 复杂的动画和关键帧定义
- 需要使用 CSS 自定义属性(变量)
- 需要精确控制 CSS 优先级
- 团队成员对 Tailwind 不熟悉
四、全局样式管理
app/globals.css 是放置全局样式的理想位置,适合存放:
- CSS 自定义属性(设计 Token)
- 字体定义
- 基础元素重置样式
- 第三方库样式覆盖
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* CSS 自定义属性:设计系统的核心 */
:root {
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-background: #ffffff;
--color-text: #0f172a;
--color-text-muted: #6b7280;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 48px;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
}
/* 暗色模式的 CSS 变量 */
.dark {
--color-background: #0f172a;
--color-text: #f1f5f9;
--color-text-muted: #94a3b8;
}
/* 基础样式重置 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
background-color: var(--color-background);
color: var(--color-text);
font-family: 'Inter', system-ui, sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 自定义滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.dark ::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
}
在根布局中引入:
// app/layout.tsx
import './globals.css'; // 仅需在根布局引入一次
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<body>{children}</body>
</html>
);
}
五、Sass/SCSS 支持
如果团队更习惯 Sass 语法,Next.js 完全支持:
npm install --save-dev sass
将 .css 文件改为 .scss 即可使用 Sass 特性:
// styles/variables.scss
$primary: #2563eb;
$border-radius: 8px;
$spacing-unit: 8px;
// components/Card.module.scss
@use '../styles/variables' as *;
.card {
border-radius: $border-radius;
padding: $spacing-unit * 3;
transition: box-shadow 0.2s ease;
&__title {
font-size: 1.25rem;
font-weight: 700;
color: $primary;
}
&__body {
margin-top: $spacing-unit * 1.5;
color: #6b7280;
}
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
六、CSS-in-JS 的注意事项
Styled Components 和 Emotion 等 CSS-in-JS 方案在 Next.js App Router 中存在重要限制:它们依赖 React Context,而 Context 在服务端组件中不可用。
这意味着使用 CSS-in-JS 时,必须将相关组件标记为客户端组件,这将增加客户端 JavaScript 体积。
如果确实需要使用 CSS-in-JS,Next.js 官方文档提供了详细配置方法。但对于新项目,建议优先考虑 Tailwind CSS 或 CSS Modules,它们与 App Router 的服务端组件完美兼容。
七、动画与过渡效果
1. CSS 过渡动画
// 使用 Tailwind 的 transition 工具类
export function AnimatedCard({ children }: { children: React.ReactNode }) {
return (
<div className="
transform transition-all duration-300 ease-in-out
hover:scale-105 hover:shadow-xl
cursor-pointer
">
{children}
</div>
);
}
2. Framer Motion:推荐的动画库
npm install framer-motion
'use client'; // 动画库需在客户端运行
import { motion, AnimatePresence } from 'framer-motion';
// 淡入动画组件
export function FadeInSection({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
);
}
// 列表动画
interface ListItem {
id: string;
name: string;
}
export function AnimatedList({ items }: { items: ListItem[] }) {
return (
<ul>
<AnimatePresence>
{items.map((item, index) => (
<motion.li
key={item.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ delay: index * 0.05 }}
>
{item.name}
</motion.li>
))}
</AnimatePresence>
</ul>
);
}
八、最佳实践总结
在实际项目中,样式管理容易变得混乱。以下建议有助于保持代码整洁:
1. 统一样式方案
同一项目中应避免混用多种样式方案。选定一种主方案,仅在特殊情况下才引入其他方案。混用 Tailwind、CSS Modules 和 CSS-in-JS 将导致维护困难。
2. 建立设计系统
避免使用"魔法数字",应建立统一的设计 Token:
// ❌ 不佳:魔法数字,维护时难以理解
<p style={{ fontSize: '14px', color: '#6b7280' }}>...</p>
// ✅ 推荐:使用设计系统中的语义化类名
<p className="text-sm text-gray-500">...</p>
3. 分离样式与逻辑
// ❌ 不佳:样式和逻辑混杂
export function UserCard({ user, isActive }: UserCardProps) {
return (
<div className={`p-4 rounded ${
isActive
? 'bg-blue-50 border-2 border-blue-500'
: 'bg-white border border-gray-200'
}`}>
{/* 更多内容 */}
</div>
);
}
// ✅ 推荐:提取变体逻辑,提升可读性
const cardVariants = {
active: 'bg-blue-50 border-2 border-blue-500',
default: 'bg-white border border-gray-200',
};
export function UserCard({ user, isActive }: UserCardProps) {
return (
<div className={`p-4 rounded ${
cardVariants[isActive ? 'active' : 'default']
}`}>
{/* 更多内容 */}
</div>
);
}
4. 使用 Storybook 进行组件开发
对于中等规模以上的项目,使用 Storybook 开发和文档化组件是值得的投资。它能有效避免"修改此处可能影响彼处"的不确定性。
5. 性能优化建议
- 避免在循环中生成动态类名
- 合理使用
will-change提示浏览器优化 - 对复杂动画使用
requestAnimationFrame - 利用 CSS
contain属性优化渲染性能
九、本章小结
通过本章学习,你应该掌握了:
- 主流样式方案的优缺点及适用场景
- Tailwind CSS 的核心用法、响应式设计和暗色模式
- CSS Modules 的作用域隔离机制
- 全局样式管理和设计 Token 的最佳实践
- 动画实现的多种方案(CSS 过渡、Framer Motion)
- 样式组织的最佳实践和性能优化技巧
下一章将深入探讨 Next.js 的图像和字体优化——这是框架开箱即用的两大性能优化特性。