1. 全局样式
在 App Router 项目中,全局样式文件通常放在 src/app/globals.css。这里适合写 CSS 变量、重置样式、以及需要作用于整站的规则;组件级样式则优先用 CSS Modules 或 Tailwind,避免全局污染。
- 位置:
src/app/globals.css - 引入方式:在根布局
src/app/layout.tsx顶部使用import "./globals.css",保证所有路由段都能继承这些基础样式。
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 将样式文件与组件绑定:构建工具会为每个类名生成唯一哈希,从而避免不同组件间的类名冲突,非常适合在大型项目中维护可预测的样式边界。
- 命名约定:样式文件使用
*.module.css(例如Button.module.css)。 - 使用方式:在组件中
import styles from "./Button.module.css",再通过className={styles.primary}引用。
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.ts、postcss.config.mjs,并在 globals.css 中注入 @tailwind 指令。你只需在组件的 className 上直接写工具类即可。
tailwind.config.ts 示例(扫描 src/app 与 src/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-in-JS,建议评估迁移成本;新项目在 Next 15 上更常采用 Tailwind + CSS Modules 的组合。
- Server Component 树中应尽量避免引入仅在客户端可用的样式运行时,以免破坏流式渲染或增大客户端包体。
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),避免额外网络往返阻塞渲染。
- Google Fonts:使用
next/font/google,例如 Inter。 - 本地字体:使用
next/font/local,将.woff2放在例如src/app/fonts/下并配置src路径。 - 通过
variable暴露 CSS 变量,便于在 Tailwind 或全局 CSS 中统一引用。
在 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 应用于 body;display.variable 可在任意子组件中用 className="font-[family-name:var(--font-display)]" 或 Tailwind 扩展的 fontFamily 使用展示字体。请将 PlayfairDisplay.woff2 实际文件放到 src/app/fonts/。
6. next/image 图片优化
next/image 提供的 Image 组件会在请求时或构建时(视静态导入与配置而定)完成尺寸优化、现代格式(如 WebP/AVIF)与懒加载,显著改善 LCP 与带宽占用。
- 本地图片:放在
src下并import hero from "@/app/hero.jpg",Next 可静态分析宽高,减少 CLS。 - 远程图片:必须在
next.config.ts的images.remotePatterns中声明允许的协议、主机与路径(安全白名单)。 - 固定尺寸:使用
width与height(或静态导入推导)。 - 响应式铺满:父级设
position: relative,Image使用fill与sizes,让浏览器选择合适的 srcset。
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。
public/robots.txt→ 浏览器请求/robots.txtpublic/favicon.ico→/favicon.icopublic/images/logo.svg→/images/logo.svg
在 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/image;public 更适合固定 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/google 与 next/font/local 自托管字体、减小 CLS,并在根布局挂载变量。
next/image
本地图静态导入;远程图配置 remotePatterns;按需选择 width/height 或 fill + sizes。
public/
根路径直链静态资源;适合 favicon、robots.txt、无需优化的固定文件。