CSS 样式
深入理解 Next.js 中的 CSS 样式方案,包括 Tailwind CSS、CSS Modules、全局 CSS、Sass 和 CSS-in-JS
概述
Next.js 提供了多种使用 CSS 样式化应用程序的方式:
- Tailwind CSS
- CSS Modules
- Global CSS
- External Stylesheets
- Sass
- CSS-in-JS
Tailwind CSS
Tailwind CSS 是一个实用优先的 CSS 框架,提供低级实用类来构建自定义设计。
安装 Tailwind CSS v4
pnpm add -D tailwindcss @tailwindcss/postcss配置 PostCSS
在 postcss.config.mjs 文件中添加 PostCSS 插件:
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}导入 Tailwind
在全局 CSS 文件中导入 Tailwind:
@import 'tailwindcss';在根布局中导入 CSS
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}使用 Tailwind 类
export default function Page() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<h1 className="text-4xl font-bold">Hello, Next.js!</h1>
</main>
)
}Tailwind CSS v3(向后兼容)
对于需要更广泛浏览器支持的项目,可以使用 Tailwind CSS v3。
安装 Tailwind v3
pnpm add -D tailwindcss@^3 postcss autoprefixer
npx tailwindcss init -p配置模板路径
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
}添加 Tailwind 指令
@tailwind base;
@tailwind components;
@tailwind utilities;CSS Modules
CSS Modules 通过自动创建唯一的类名来局部作用域 CSS,允许你在不同文件中使用相同的类名而不会发生冲突。
基本使用
.dashboard {
padding: 24px;
}
.title {
font-size: 2rem;
font-weight: bold;
}import styles from './styles.module.css'
export default function Dashboard() {
return (
<div className={styles.dashboard}>
<h1 className={styles.title}>Dashboard</h1>
</div>
)
}组合类名
import styles from './page.module.css'
export default function Page() {
return (
<div className={`${styles.container} ${styles.active}`}>
<p className={styles.text}>Hello World</p>
</div>
)
}全局类名
CSS Modules 也支持 :global 选择器来定义全局样式:
.container {
padding: 20px;
}
:global(.global-class) {
color: red;
}Global CSS
全局样式可以导入到任何布局、页面或组件中。
导入全局样式
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}全局 CSS 文件示例
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
}
a {
color: inherit;
text-decoration: none;
}注意事项
全局样式可以导入到 app 目录中的任何布局、页面或组件。但是,由于 Next.js 使用 React 的内置样式表支持与 Suspense 集成,这目前不会在路由之间导航时删除样式表,这可能导致冲突。
建议:
- 对真正的全局 CSS(如 Tailwind 的基础样式)使用全局样式
- 对组件样式使用 Tailwind CSS
- 在需要时对自定义作用域 CSS 使用 CSS Modules
External Stylesheets
外部包发布的样式表可以导入到 app 目录的任何位置,包括共同定位的组件:
import 'bootstrap/dist/css/bootstrap.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}导入组件级样式
import { Card } from '@/components/card'
import '@/components/card/styles.css'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<Card />
{children}
</div>
)
}Sass
Next.js 内置支持在安装包后使用 .scss 和 .sass 扩展名集成 Sass。
安装 Sass
npm install --save-dev sass使用 Sass
$primary-color: #333;
.container {
padding: 20px;
h1 {
color: $primary-color;
font-size: 2rem;
}
}import './styles.scss'
export default function Page() {
return (
<div className="container">
<h1>Hello, Sass!</h1>
</div>
)
}使用 Sass Modules
$button-bg: #0070f3;
$button-hover: #0051cc;
.button {
background-color: $button-bg;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: $button-hover;
}
}import styles from './button.module.scss'
export function Button() {
return <button className={styles.button}>Click me</button>
}自定义 Sass 选项
在 next.config.js 中配置 Sass 选项:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
sassOptions: {
additionalData: `$var: red;`,
},
}
export default nextConfig指定 Sass 实现
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
sassOptions: {
implementation: 'sass-embedded',
},
}
export default nextConfigSass 变量导出
$primary-color: #64ff00;
:export {
primaryColor: $primary-color;
}import variables from './variables.module.scss'
export default function Page() {
return <h1 style={{ color: variables.primaryColor }}>Hello, Next.js!</h1>
}CSS-in-JS
使用 CSS-in-JS 与 Server Components 和 Streaming 等新 React 功能需要库作者支持最新版本的 React,包括并发渲染。
支持的库
以下库在 app 目录的 Client Components 中受支持:
ant-designchakra-ui@fluentui/react-componentskuma-ui@mui/material@mui/joypandacssstyled-jsxstyled-componentsstylextamaguitss-reactvanilla-extract
styled-jsx
在 Client Components 中使用 styled-jsx 需要使用 v5.1.0 或更高版本。
'use client'
export default function Page() {
return (
<div>
<h1>Hello, styled-jsx!</h1>
<style jsx>{`
h1 {
color: blue;
font-size: 2rem;
}
`}</style>
</div>
)
}styled-components
配置 styled-components 是一个三步选择加入过程。
1. 创建样式注册表
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode
}) {
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}2. 在根布局中包装子组件
import StyledComponentsRegistry from './lib/registry'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
)
}3. 使用 styled-components
'use client'
import styled from 'styled-components'
const Title = styled.h1`
color: #0070f3;
font-size: 2rem;
`
const Container = styled.div`
padding: 20px;
background-color: #f5f5f5;
`
export default function Page() {
return (
<Container>
<Title>Hello, styled-components!</Title>
</Container>
)
}排序和导入
CSS 导入顺序
为了保持 CSS 排序的可预测性:
- 尽量将 CSS 导入包含在单个 JavaScript 或 TypeScript 入口文件中
- 在应用程序的根部导入全局样式和 Tailwind 样式表
- 对大多数样式需求使用 Tailwind CSS
- 当 Tailwind 实用程序不足时,对组件特定样式使用 CSS Modules
- 为 CSS 模块使用一致的命名约定(例如,使用
<name>.module.css而不是<name>.tsx) - 将共享样式提取到共享组件中以避免重复导入
- 关闭自动排序导入的 linter 或格式化程序,如 ESLint 的
sort-imports
示例:正确的导入顺序
// 1. 全局样式
import './globals.css'
// 2. 外部库样式
import 'bootstrap/dist/css/bootstrap.css'
// 3. 组件
import { Header } from '@/components/header'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<Header />
{children}
</body>
</html>
)
}开发与生产
开发环境
在开发环境中(next dev):
- CSS 更新通过 Fast Refresh 即时应用
- JavaScript 是 Fast Refresh 所必需的
生产环境
在生产环境中(next build):
- 所有 CSS 文件自动连接成多个最小化和代码分割的
.css文件 - 确保为路由加载最少量的 CSS
- 即使禁用 JavaScript,CSS 仍然加载
注意事项
- CSS 排序在开发中可能表现不同,始终确保检查构建(
next build)以验证最终的 CSS 顺序
CSS 分块
可以使用 next.config.js 中的 cssChunking 选项控制 CSS 的分块方式:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
cssChunking: 'loose', // 或 'strict'
},
}
export default nextConfig最佳实践
1. 选择合适的样式方案
- Tailwind CSS:用于大多数样式需求,涵盖常见设计模式
- CSS Modules:用于组件特定样式,当 Tailwind 实用程序不足时
- Global CSS:用于真正的全局 CSS(如重置样式、基础样式)
- Sass:当需要变量、嵌套和混合等功能时
- CSS-in-JS:当需要动态样式或主题化时
2. 使用 CSS Modules 进行组件隔离
import styles from './card.module.css'
export function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className={styles.card}>
<h2 className={styles.title}>{title}</h2>
<div className={styles.content}>{children}</div>
</div>
)
}.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 20px;
background-color: white;
}
.title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 12px;
}
.content {
color: #6b7280;
}3. 结合 Tailwind 和 CSS Modules
import styles from './button.module.css'
export function Button({ variant = 'primary', children }: {
variant?: 'primary' | 'secondary'
children: React.ReactNode
}) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
)
}.button {
@apply px-4 py-2 rounded font-medium transition-colors;
}
.primary {
@apply bg-blue-600 text-white hover:bg-blue-700;
}
.secondary {
@apply bg-gray-200 text-gray-800 hover:bg-gray-300;
}4. 使用 Sass 变量和混合
$primary-color: #0070f3;
$secondary-color: #7928ca;
$border-radius: 8px;
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}@import '@/styles/variables';
.hero {
@include flex-center;
min-height: 100vh;
background: linear-gradient(to right, $primary-color, $secondary-color);
h1 {
color: white;
font-size: 3rem;
}
}5. 优化 CSS 性能
// 只在根布局导入全局样式
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}// 在特定布局导入特定样式
import './dashboard.css'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <div className="dashboard">{children}</div>
}6. 使用类型安全的 CSS Modules
import styles from './card.module.css'
// TypeScript 会自动推断 styles 的类型
export function Card() {
return (
<div className={styles.card}>
{/* TypeScript 会提示可用的类名 */}
<h2 className={styles.title}>Title</h2>
</div>
)
}常见模式
响应式设计
使用 Tailwind 的响应式前缀:
export default function Page() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="p-4 bg-white rounded shadow">Card 1</div>
<div className="p-4 bg-white rounded shadow">Card 2</div>
<div className="p-4 bg-white rounded shadow">Card 3</div>
</div>
)
}暗色模式
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html className="dark">
<body className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
{children}
</body>
</html>
)
}@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}动态类名
type BadgeVariant = 'success' | 'warning' | 'error'
const variantStyles: Record<BadgeVariant, string> = {
success: 'bg-green-100 text-green-800',
warning: 'bg-yellow-100 text-yellow-800',
error: 'bg-red-100 text-red-800',
}
export function Badge({
variant,
children
}: {
variant: BadgeVariant
children: React.ReactNode
}) {
return (
<span className={`px-2 py-1 rounded text-sm ${variantStyles[variant]}`}>
{children}
</span>
)
}总结
Next.js 提供了灵活而强大的 CSS 样式方案:
- Tailwind CSS:实用优先的 CSS 框架,适合快速开发
- CSS Modules:局部作用域的 CSS,避免类名冲突
- Global CSS:全局样式,用于基础样式和重置
- Sass:CSS 预处理器,提供变量、嵌套和混合等功能
- CSS-in-JS:动态样式和主题化,需要客户端组件
- 最佳实践:结合使用多种方案,保持 CSS 排序可预测
通过合理选择和组合这些样式方案,可以构建既美观又高性能的现代 Web 应用。
上次更新于