← 返回目录

第五章:样式与资源

CSS Modules、Tailwind、字体与图片优化

1. 全局样式

在 App Router 项目中,全局样式文件通常放在 src/app/globals.css。这里适合写 CSS 变量、重置样式、以及需要作用于整站的规则;组件级样式则优先用 CSS Modules 或 Tailwind,避免全局污染。

globals.css 示例(可与 Tailwind 的 @tailwind 指令共存):

@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --background: #ffffff;
  --foreground: #0f172a;
}

body {
  color: var(--foreground);
  background: var(--background);
}

layout.tsx 中导入:

import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "My App",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="zh-CN">
      <body className="antialiased min-h-screen">{children}</body>
    </html>
  );
}

2. CSS Modules

CSS Modules 将样式文件与组件绑定:构建工具会为每个类名生成唯一哈希,从而避免不同组件间的类名冲突,非常适合在大型项目中维护可预测的样式边界。

Button.module.css

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  font-weight: 600;
  transition: background-color 0.2s ease;
}

.primary {
  background-color: #0ea5e9;
  color: #ffffff;
}

.primary:hover {
  background-color: #0284c7;
}

.secondary {
  background-color: #f1f5f9;
  color: #0f172a;
}

Button.tsx(默认即为 Server Component,无需 "use client"):

import clsx from "clsx";
import styles from "./Button.module.css";

type ButtonProps = {
  variant?: "primary" | "secondary";
  className?: string;
  children: React.ReactNode;
};

export default function Button({
  variant = "primary",
  className,
  children,
}: ButtonProps) {
  return (
    <button
      type="button"
      className={clsx(styles.button, styles[variant], className)}
    >
      {children}
    </button>
  );
}

组合多个类名:除 clsx / classnames 外,也可用模板字符串拼接,例如 {`${styles.button} ${styles.primary}`};条件较多时更推荐 clsx

安装 clsx:

npm install clsx

安装后 package.json 中会多出依赖项(节选):

{
  "dependencies": {
    "clsx": "^2.1.1",
    "next": "^15.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}

3. Tailwind CSS

通过 create-next-app 勾选 Tailwind 时,脚手架会写好 tailwind.config.tspostcss.config.mjs,并在 globals.css 中注入 @tailwind 指令。你只需在组件的 className 上直接写工具类即可。

tailwind.config.ts 示例(扫描 src/appsrc/components):

import type { Config } from "tailwindcss";

const config: Config = {
  content: [
    "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        brand: {
          DEFAULT: "#0ea5e9",
          foreground: "#f0f9ff",
        },
      },
      fontFamily: {
        sans: ["var(--font-inter)", "system-ui", "sans-serif"],
      },
    },
  },
  plugins: [],
};

export default config;

在组件中直接使用工具类:

export default function Hero() {
  return (
    <section className="mx-auto max-w-3xl px-4 py-16 text-center">
      <h1 className="text-4xl font-bold tracking-tight text-slate-900">
        欢迎
      </h1>
      <p className="mt-4 text-lg text-slate-600">
        Tailwind 与 Next.js App Router 配合使用时,无需额外运行时 CSS-in-JS。
      </p>
    </section>
  );
}

💡 要点

Tailwind CSS 与 React Server Components 天然契合:类名在构建阶段由 Tailwind 扫描并生成最终 CSS,运行时不会在浏览器里注入样式表脚本;因此你可以在 Server Component 中随意书写 className,而不必像部分 CSS-in-JS 方案那样强制使用客户端组件。

4. CSS-in-JS 注意事项

styled-components@emotion/react 等库依赖 React 运行时与客户端上下文来生成与挂载样式。在 App Router 与 RSC 模型下,需要额外配置(例如 Next 官方文档中的 styled-components Registry 模式),且样式组件通常只能用于 Client Components(文件顶部添加 "use client")。

CSS Modules vs Tailwind vs CSS-in-JS

维度 CSS Modules Tailwind CSS CSS-in-JS(styled 等)
作用域 构建期哈希,局部作用域 工具类 + Purge,全局原子类 通常随组件封装,运行时注入
Server Components ✅ 完全支持 ✅ 完全支持 ⚠️ 多数需 Client + 配置
学习曲线 熟悉 CSS 即可 需记忆类名或依赖插件 与 React 组件模型一致
包体与性能 仅打包用到的模块 JIT 生成,体积小 运行时开销因库而异

5. next/font 字体优化

next/font 在构建时下载或引用字体文件,并自动生成 @font-face 规则与 font-display 策略,减少布局抖动(CLS),避免额外网络往返阻塞渲染。

layout.tsx 中组合 Google 字体与本地字体:

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import localFont from "next/font/local";
import "./globals.css";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-inter",
});

const display = localFont({
  src: "./fonts/PlayfairDisplay.woff2",
  weight: "700",
  style: "normal",
  display: "swap",
  variable: "--font-display",
});

export const metadata: Metadata = {
  title: {
    default: "My App",
    template: "%s | My App",
  },
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="zh-CN"
      className={`${inter.variable} ${display.variable}`}
    >
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}

说明:inter.className 将 Inter 应用于 bodydisplay.variable 可在任意子组件中用 className="font-[family-name:var(--font-display)]" 或 Tailwind 扩展的 fontFamily 使用展示字体。请将 PlayfairDisplay.woff2 实际文件放到 src/app/fonts/

6. next/image 图片优化

next/image 提供的 Image 组件会在请求时或构建时(视静态导入与配置而定)完成尺寸优化、现代格式(如 WebP/AVIF)与懒加载,显著改善 LCP 与带宽占用。

next.config.ts 中允许远程图床域名示例:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "images.unsplash.com",
        port: "",
        pathname: "/**",
      },
    ],
  },
};

export default nextConfig;

组件示例:静态导入 + 远程 URL + fill

import Image from "next/image";
import localPic from "./photo.jpg";

export default function Gallery() {
  return (
    <div className="space-y-8">
      <Image
        src={localPic}
        alt="本地打包优化的图片"
        placeholder="blur"
        className="rounded-lg"
        priority
      />

      <div className="relative h-64 w-full max-w-2xl">
        <Image
          src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee"
          alt="远程示例"
          fill
          sizes="(max-width: 768px) 100vw, 672px"
          className="object-cover rounded-lg"
        />
      </div>
    </div>
  );
}

7. 静态资源(public 目录)

项目根目录下的 public/ 用于存放原样提供的静态文件:构建时复制到输出根路径,访问时以站点根 URL 为前缀,无需 import

在 JSX 中引用(普通 <img> 或 Link):

import Link from "next/link";

export default function FooterBrand() {
  return (
    <Link href="/" className="inline-flex items-center gap-2">
      {/* eslint-disable-next-line @next/next/no-img-element */}
      <img src="/images/logo.svg" alt="站点 Logo" width={32} height={32} />
      <span>My Site</span>
    </Link>
  );
}

若需要自动优化与懒加载,优先将图片放在 src 内并用 next/imagepublic 更适合固定 URL 资源、favicon、manifest.json 等。

8. 本章要点

全局与模块化

layout.tsx 引入 globals.css;组件样式用 *.module.css 避免全局污染。

Tailwind

默认集成于官方模板;在 tailwind.config.ts 扩展主题;类名在构建期生成,与 Server Components 兼容。

CSS-in-JS

styled-components / Emotion 等需额外配置且多用于 Client Components;新项常选 Tailwind + CSS Modules。

next/font

next/font/googlenext/font/local 自托管字体、减小 CLS,并在根布局挂载变量。

next/image

本地图静态导入;远程图配置 remotePatterns;按需选择 width/heightfill + sizes

public/

根路径直链静态资源;适合 favicon、robots.txt、无需优化的固定文件。