Next.js

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 nextConfig

Sass 变量导出

$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-design
  • chakra-ui
  • @fluentui/react-components
  • kuma-ui
  • @mui/material
  • @mui/joy
  • pandacss
  • styled-jsx
  • styled-components
  • stylex
  • tamagui
  • tss-react
  • vanilla-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 排序的可预测性:

  1. 尽量将 CSS 导入包含在单个 JavaScript 或 TypeScript 入口文件中
  2. 在应用程序的根部导入全局样式和 Tailwind 样式表
  3. 对大多数样式需求使用 Tailwind CSS
  4. 当 Tailwind 实用程序不足时,对组件特定样式使用 CSS Modules
  5. 为 CSS 模块使用一致的命名约定(例如,使用 <name>.module.css 而不是 <name>.tsx
  6. 将共享样式提取到共享组件中以避免重复导入
  7. 关闭自动排序导入的 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 样式方案:

  1. Tailwind CSS:实用优先的 CSS 框架,适合快速开发
  2. CSS Modules:局部作用域的 CSS,避免类名冲突
  3. Global CSS:全局样式,用于基础样式和重置
  4. Sass:CSS 预处理器,提供变量、嵌套和混合等功能
  5. CSS-in-JS:动态样式和主题化,需要客户端组件
  6. 最佳实践:结合使用多种方案,保持 CSS 排序可预测

通过合理选择和组合这些样式方案,可以构建既美观又高性能的现代 Web 应用。

在 GitHub 上编辑

上次更新于