跳到主要内容

Next.js 核心知识点

一、基础概念

1. 项目结构

Next.js 项目结构约定优于配置,主要有两种主流结构:app 目录(推荐,自 Next.js 13 起)和 pages 目录。

app 目录结构 (App Router)

app 目录引入了新的文件约定和 React Server Components,提供了更灵活的布局和路由组织方式。

说明:

  • layout.tsx: 定义共享 UI 布局,必须包含 <html><body> 标签。
  • page.tsx: 定义路由段的唯一 UI,是页面的主要内容。
  • loading.tsx: 定义加载状态 UI,使用 React Suspense 实现。
  • error.tsx: 定义错误处理 UI,捕获路由段及其子段的错误。
  • template.tsx: 类似于 layout.tsx,但在导航时会重新挂载,状态不会保留。
  • route.ts (或 .js): 定义 API 路由处理程序 (Route Handlers)。

示例:

app/
├── layout.tsx # 根布局
├── page.tsx # 首页 (/)
├── loading.tsx # 根加载 UI
├── error.tsx # 根错误 UI

├── dashboard/ # /dashboard 路由段
│ ├── layout.tsx # dashboard 布局
│ ├── page.tsx # /dashboard 页面
│ └── settings/ # /dashboard/settings 路由段
│ └── page.tsx # /dashboard/settings 页面

└── api/ # API 路由组
└── hello/
└── route.ts # /api/hello API 路由

pages 目录结构 (Pages Router)

pages 目录是 Next.js 传统的路由方式,每个 .js, .jsx, .ts, 或 .tsx 文件都映射到一个路由。

说明:

  • _app.tsx: 自定义应用入口,用于包裹所有页面,可注入全局 CSS、共享状态等。
  • _document.tsx: 自定义 HTML 文档结构,用于修改 <html>, <body> 标签,添加服务端渲染的 head 内容。
  • index.tsx: 根目录下的 index.tsx 对应 / 路由。
  • api/: 此目录下的文件定义 API 路由。

示例:

pages/
├── _app.tsx # 自定义 App
├── _document.tsx # 自定义 Document
├── index.tsx # 首页 (/)
├── about.tsx # /about 页面

├── posts/
│ ├── index.tsx # /posts 页面
│ └── [slug].tsx # /posts/:slug 动态页面

└── api/
└── user.ts # /api/user API 路由

public 静态资源

说明: public 目录用于存放静态资源,如图片、字体、favicon.ico 等。此目录下的文件会从应用的根路径 / 开始提供服务。例如 public/logo.png 可以通过 /logo.png 访问。

示例:

public/
├── favicon.ico
└── images/
└── logo.png # 可通过 /images/logo.png 访问

next.config.js

说明: 项目根目录下的 next.config.js 文件用于自定义 Next.js 的行为,例如配置环境变量、重定向、图片优化、Webpack 配置等。

示例:

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true, // 启用 React 严格模式
images: {
domains: ["example.com"], // 配置允许加载图片的外部域名
},
env: {
CUSTOM_ENV_VAR: "my-value", // 定义环境变量
},
async redirects() {
// 配置重定向规则
return [
{
source: "/old-path",
destination: "/new-path",
permanent: true, // true 表示 308 永久重定向, false 表示 307 临时重定向
},
];
},
// ... 其他配置
};

module.exports = nextConfig;

特殊文件命名规则 (App Router)

说明:app 目录中,特定的文件名有特殊的含义,用于构建 UI 和定义行为。

  • layout.js / .tsx: 定义共享布局。
  • page.js / .tsx: 定义页面 UI。
  • loading.js / .tsx: 定义加载 UI (基于 Suspense)。
  • not-found.js / .tsx: 定义未找到 (404) UI。
  • error.js / .tsx: 定义错误 UI (捕获运行时错误)。
  • global-error.js / .tsx: 定义全局错误 UI (捕获根 layout 错误)。
  • route.js / .ts: 定义 API 路由端点 (Route Handlers)。
  • template.js / .tsx: 类似 layout,但每次导航都重新渲染。
  • default.js / .tsx: 并行路由的后备 UI。

路由约定

说明: Next.js 的核心特性是基于文件系统的路由。

  • App Router (app): 文件夹定义路由段,page.tsx 文件使该段公开可访问。
  • Pages Router (pages): 文件名直接映射到路由路径(index 文件映射到根路径)。

2. 渲染模式

Next.js 支持多种渲染模式,可以按需为不同页面选择最合适的策略。

服务端渲染 (SSR - Server-Side Rendering)

说明: 每次请求时,在服务器上动态生成页面的 HTML。适用于需要实时数据或用户特定内容的页面。

实现 (pages router): 使用 getServerSideProps 函数。 实现 (app router): 默认情况下,Server Components 就是动态渲染的,除非数据获取被缓存或路由是静态导出的。可以通过设置 dynamic = 'force-dynamic' 或使用动态函数(如 headers(), cookies())来确保动态渲染。

示例 (pages router - getServerSideProps):

// pages/profile.tsx
import { GetServerSideProps, InferGetServerSidePropsType } from "next";

export const getServerSideProps: GetServerSideProps = async (context) => {
// context 对象包含请求信息,如 req, res, query 等
const userId = context.query.userId || "default";
// 在服务端获取数据
const res = await fetch(`https://api.example.com/users/${userId}`);
const userData = await res.json();

// 如果数据获取失败,可以返回 notFound 或 redirect
if (!userData) {
return {
notFound: true,
};
}

// 将数据通过 props 传递给页面组件
return {
props: { userData }, // props 会被序列化
};
};

function ProfilePage({
userData,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
// 页面组件接收 props
return <div>欢迎, {userData.name}</div>;
}

export default ProfilePage;

示例 (app router - 动态 Server Component):

// app/profile/[userId]/page.tsx

// 默认情况下 Server Component 就是动态渲染的
// 如果需要强制动态,可以导出 dynamic 变量或使用动态函数
// export const dynamic = 'force-dynamic'; // 强制动态渲染

// 或者,使用像 headers(), cookies() 这样的动态函数也会使其动态
import { headers } from "next/headers";

async function getUserData(userId: string) {
// 使用 headers() 使其动态化
const headerList = headers();
const res = await fetch(`https://api.example.com/users/${userId}`);
const userData = await res.json();
return userData;
}

export default async function ProfilePage({
params,
}: {
params: { userId: string };
}) {
const userData = await getUserData(params.userId);

if (!userData) {
// 可以使用 notFound() 函数触发 404
// notFound();
return <div>用户未找到</div>;
}

return <div>欢迎, {userData.name}</div>;
}

静态生成 (SSG - Static Site Generation)

说明: 在构建时生成页面的 HTML。生成的 HTML 可以部署到 CDN,提供极快的加载速度。适用于内容不经常变化的页面,如博客文章、文档、营销页面。

实现 (pages router): 使用 getStaticProps 函数。 实现 (app router): 默认情况下,如果 Server Component 的数据获取是静态的(例如,fetch 结果被自动缓存且未使用动态函数),Next.js 会尝试静态生成该页面。

示例 (pages router - getStaticProps):

// pages/posts/[slug].tsx
import { GetStaticProps, GetStaticPaths, InferGetStaticPropsType } from "next";
import fs from "fs";
import path from "path";
import matter from "gray-matter"; // 假设使用 gray-matter 解析 markdown

export const getStaticPaths: GetStaticPaths = async () => {
// 读取所有 markdown 文件名作为可能的路径
const postsDirectory = path.join(process.cwd(), "posts");
const filenames = fs.readdirSync(postsDirectory);
const paths = filenames.map((filename) => ({
params: { slug: filename.replace(/\.md$/, "") },
}));

// fallback: false 表示未匹配的路径返回 404
// fallback: true 表示尝试动态生成页面 (需要处理加载状态)
// fallback: 'blocking' 表示服务端渲染未生成的路径,然后缓存
return { paths, fallback: false };
};

export const getStaticProps: GetStaticProps = async (context) => {
const slug = context.params?.slug as string;
const filePath = path.join(process.cwd(), "posts", `${slug}.md`);
const fileContents = fs.readFileSync(filePath, "utf8");
const { data, content } = matter(fileContents); // 解析 markdown 元数据和内容

return {
props: {
post: {
slug,
frontmatter: data,
content,
},
},
};
};

function PostPage({ post }: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<article>
<h1>{post.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} /> {/* 假设 content 是 HTML */}
</article>
);
}

export default PostPage;

示例 (app router - 静态数据获取):

// app/posts/[slug]/page.tsx
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { notFound } from "next/navigation";

// generateStaticParams 告诉 Next.js 要为哪些参数预先生成静态页面
// 类似于 pages router 中的 getStaticPaths
export async function generateStaticParams() {
const postsDirectory = path.join(process.cwd(), "posts");
const filenames = fs.readdirSync(postsDirectory);
return filenames.map((filename) => ({
slug: filename.replace(/\.md$/, ""),
}));
}

async function getPostData(slug: string) {
const filePath = path.join(process.cwd(), "posts", `${slug}.md`);
try {
const fileContents = fs.readFileSync(filePath, "utf8");
const { data, content } = matter(fileContents);
return { frontmatter: data, content };
} catch (error) {
return null; // 文件不存在或其他错误
}
}

export default async function PostPage({
params,
}: {
params: { slug: string };
}) {
const post = await getPostData(params.slug);

if (!post) {
notFound(); // 如果帖子不存在,显示 not-found.tsx UI
}

// 这个 fetch 默认会被 Next.js 缓存,有助于静态生成
// const staticData = await fetch('https://api.example.com/static-data', { cache: 'force-cache' }).then(res => res.json());

return (
<article>
<h1>{post.frontmatter.title}</h1>
<div>{post.content}</div> {/* 这里假设 content 只是文本 */}
</article>
);
}

增量静态再生成 (ISR - Incremental Static Regeneration)

说明: SSG 的扩展。在构建时生成页面,但允许在流量进入时按需(或按时间间隔)重新生成页面,无需重新构建整个站点。结合了 SSG 的性能优势和 SSR 的内容新鲜度。

实现 (pages router):getStaticProps 返回的对象中添加 revalidate 属性(秒数)。 实现 (app router):fetch 调用中使用 next: { revalidate: number } 选项,或者在页面级别导出 revalidate 变量。

示例 (pages router - ISR):

// pages/products/[id].tsx
import { GetStaticProps, GetStaticPaths, InferGetStaticPropsType } from "next";

export const getStaticPaths: GetStaticPaths = async () => {
// 假设只预构建几个热门产品
return {
paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
fallback: "blocking", // 未预构建的页面会 SSR,然后缓存
};
};

export const getStaticProps: GetStaticProps = async (context) => {
const id = context.params?.id as string;
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();

if (!product) {
return { notFound: true };
}

return {
props: { product },
revalidate: 60, // 每 60 秒最多重新生成一次页面
};
};

function ProductPage({
product,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div>
{product.name} - ${product.price}
</div>
);
}

export default ProductPage;

示例 (app router - ISR):

// app/products/[id]/page.tsx
import { notFound } from "next/navigation";

// 使用 fetch 的 revalidate 选项
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 60 }, // 数据每 60 秒重新验证一次
});
if (!res.ok) return null;
return res.json();
}

// 或者在页面级别导出 revalidate (将应用于此页面的所有 fetch)
// export const revalidate = 60;

export default async function ProductPage({
params,
}: {
params: { id: string };
}) {
const product = await getProduct(params.id);

if (!product) {
notFound();
}

return (
<div>
{product.name} - ${product.price}
</div>
);
}

// 注意:对于动态路由,仍建议使用 generateStaticParams 预构建部分页面
// export async function generateStaticParams() { ... }

客户端渲染 (CSR - Client-Side Rendering)

说明: 页面的 HTML 骨架由服务器发送,主要内容通过 JavaScript 在浏览器中渲染。适用于高度交互、数据频繁变化的仪表板等应用。Next.js 中通常通过在页面加载后使用 useEffect 或 SWR、React Query 等库获取数据来实现。

实现 (app router): 使用 'use client' 指令将组件标记为客户端组件。 实现 (pages router): 组件本身就是客户端组件,在 useEffect 或类似钩子中获取数据。

示例 (app router - Client Component):

// app/dashboard/page.tsx
"use client"; // 标记为客户端组件

import { useState, useEffect } from "react";
import useSWR from "swr"; // 示例:使用 SWR 进行数据获取

const fetcher = (url: string) => fetch(url).then((res) => res.json());

export default function DashboardPage() {
const { data, error } = useSWR("/api/user/stats", fetcher); // CSR 数据获取

if (error) return <div>加载失败</div>;
if (!data) return <div>加载中...</div>;

return (
<div>
<h1>仪表盘</h1>
<p>欢迎回来, {data.username}!</p>
<p>统计数据: {data.stats}</p>
</div>
);
}

混合渲染策略

说明: Next.js 的强大之处在于可以在同一个应用中混合使用不同的渲染策略。例如,营销页面使用 SSG,博客文章使用 ISR,用户仪表盘使用 SSR 或 CSR。App Router 通过 Server Components 和 Client Components 的组合,使得这种混合更加自然和灵活。

3. 数据获取

数据获取是 Web 应用的核心,Next.js 提供了多种方式在不同渲染模式下获取数据。

getStaticProps (Pages Router)

说明: 仅用于 pages 目录。在构建时(SSG)或重新生成时(ISR)在服务端运行,用于获取页面静态生成所需的数据。返回的数据作为 props 传递给页面组件。

示例: (见上方 SSG 部分)

getServerSideProps (Pages Router)

说明: 仅用于 pages 目录。在每次页面请求时在服务端运行(SSR)。用于获取页面动态渲染所需的数据。返回的数据作为 props 传递给页面组件。

示例: (见上方 SSR 部分)

getStaticPaths (Pages Router)

说明: 仅用于 pages 目录中的动态路由 ([param].js)。与 getStaticProps 配合使用,用于指定哪些动态路径需要在构建时进行静态生成。

示例: (见上方 SSG 部分)

数据获取 (App Router)

说明:app 目录中,推荐使用 fetch API 在 Server Components 中直接获取数据。Next.js 扩展了 fetch,提供了自动缓存、重新验证 (revalidation) 和静态生成 (SSG/ISR) 的能力。对于客户端组件 ('use client'),则使用传统方法(如 useEffect, SWR, React Query)。

示例 (Server Component 数据获取):

// app/some-page/page.tsx

async function getData() {
// 默认: { cache: 'force-cache' } - 尽可能缓存,用于 SSG
const staticRes = await fetch("https://api.example.com/static");
const staticData = await staticRes.json();

// 禁用缓存,每次请求都重新获取,用于 SSR
const dynamicRes = await fetch("https://api.example.com/dynamic", {
cache: "no-store",
});
const dynamicData = await dynamicRes.json();

// ISR: 缓存数据,但在 10 秒后重新验证
const isrRes = await fetch("https://api.example.com/isr-data", {
next: { revalidate: 10 },
});
const isrData = await isrRes.json();

return { staticData, dynamicData, isrData };
}

export default async function SomePage() {
const { staticData, dynamicData, isrData } = await getData();

return (
<div>
<section>
<h2>静态数据 (SSG/Cached)</h2>
<pre>{JSON.stringify(staticData, null, 2)}</pre>
</section>
<section>
<h2>动态数据 (SSR)</h2>
<pre>{JSON.stringify(dynamicData, null, 2)}</pre>
</section>
<section>
<h2>ISR 数据 (Revalidated)</h2>
<pre>{JSON.stringify(isrData, null, 2)}</pre>
</section>
</div>
);
}

useRouter

说明: 用于客户端组件的 React Hook,提供访问路由器对象的方法和属性,如当前路径、查询参数,以及编程式导航 (push, replace)。在 app router 中,需要从 next/navigation 导入;在 pages router 中,从 next/router 导入。

示例 (app router):

"use client";

import { useRouter, usePathname, useSearchParams } from "next/navigation";

function MyComponent() {
const router = useRouter();
const pathname = usePathname(); // 获取当前路径名,例如 '/dashboard'
const searchParams = useSearchParams(); // 获取 URL 查询参数
const userId = searchParams.get("id"); // 获取名为 'id' 的查询参数

const navigateToHome = () => {
router.push("/"); // 导航到首页
};

return (
<div>
<p>当前路径: {pathname}</p>
{userId && <p>用户 ID: {userId}</p>}
<button onClick={navigateToHome}>返回首页</button>
</div>
);
}

动态导入 (next/dynamic)

说明: 允许延迟加载 React 组件或 JavaScript 模块。这对于优化初始加载性能非常有用,可以将非首屏或不常用的代码分割出去,只在需要时加载。

示例:

import dynamic from "next/dynamic";
import { useState } from "react";

// 动态导入一个组件,禁用 SSR (只在客户端加载)
const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
ssr: false, // 禁用服务端渲染
loading: () => <p>加载重量级组件中...</p>, // 加载时的占位符
});

export default function HomePage() {
const [showHeavy, setShowHeavy] = useState(false);

return (
<div>
<h1>首页</h1>
<button onClick={() => setShowHeavy(true)}>加载重量级组件</button>
{showHeavy && <HeavyComponent />}
</div>
);
}

API Routes / Route Handlers

说明: 允许在 Next.js 应用中创建后端 API 端点。

  • Pages Router:pages/api 目录下创建文件。每个文件导出一个处理函数,接收 req (Node.js HTTP IncomingMessage) 和 res (Node.js HTTP ServerResponse)。
  • App Router:app 目录下创建 route.ts (或 .js) 文件。导出对应 HTTP 方法 (GET, POST, PUT, DELETE 等) 的异步函数,接收 request (Request 对象) 和一个包含 params 的上下文对象。

示例 (pages router - API Route):

// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from "next";

type Data = {
message: string;
};

export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
if (req.method === "GET") {
res.status(200).json({ message: "你好,世界!" });
} else {
res.setHeader("Allow", ["GET"]);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}

示例 (app router - Route Handler):

// app/api/items/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";

// 处理 GET 请求 /api/items/:id
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const itemId = params.id;
// 从数据库或其他来源获取数据...
const item = { id: itemId, name: `物品 ${itemId}` };

if (!item) {
// 如果物品不存在,返回 404
return NextResponse.json({ error: "Item not found" }, { status: 404 });
}

// 使用 NextResponse 返回 JSON 响应
return NextResponse.json(item);
}

// 处理 POST 请求 /api/items/:id
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const itemId = params.id;
const body = await request.json(); // 解析请求体

// 处理创建或更新逻辑...
console.log(`更新物品 ${itemId}:`, body);

return NextResponse.json({ message: `物品 ${itemId} 已更新`, data: body });
}

// 可以类似地添加 PUT, DELETE, PATCH, HEAD, OPTIONS 等处理函数

二、路由系统

Next.js 的路由系统是其核心功能之一,提供了声明式、基于文件系统的路由。

1. 页面路由 (App Router 优先)

文件系统路由

说明: 目录结构定义了 URL 路径。

  • app/dashboard/settings/page.tsx -> /dashboard/settings
  • pages/products/index.tsx -> /products
  • pages/products/detail.tsx -> /products/detail

动态路由

说明: 使用方括号 [] 来创建动态路由段。

示例 (app router): app/blog/[slug]/page.tsx 匹配 /blog/post-1, /blog/another-post 等。slug 的值可以通过页面组件的 params 属性访问。

// app/blog/[slug]/page.tsx
export default function BlogPostPage({ params }: { params: { slug: string } }) {
return <div>文章 Slug: {params.slug}</div>;
}

示例 (pages router): pages/users/[userId].tsx 匹配 /users/123, /users/abc 等。userId 的值可以通过 useRoutergetServerSideProps/getStaticPropscontext.params 访问。

// pages/users/[userId].tsx
import { useRouter } from "next/router";

export default function UserProfilePage() {
const router = useRouter();
const { userId } = router.query; // 获取动态参数

return <div>用户 ID: {userId}</div>;
}

嵌套路由

说明: 通过文件夹嵌套来创建嵌套路由。

示例: app/shop/categories/[categoryId]/products/[productId]/page.tsx 匹配 /shop/categories/books/products/123

捕获所有路由 (Catch-all Routes) & 可选捕获所有路由 (Optional Catch-all Routes)

说明:

  • 捕获所有路由 [...slug]: 匹配该层级及其下的所有路径。例如 app/docs/[...slug]/page.tsx 会匹配 /docs/a, /docs/a/b, /docs/a/b/c 等。params.slug 会是一个字符串数组 ['a'], ['a', 'b'], ['a', 'b', 'c']
  • 可选捕获所有路由 [[...slug]]: 类似捕获所有路由,但它也匹配没有参数的路径本身。例如 app/optional-docs/[[...slug]]/page.tsx 会匹配 /optional-docs, /optional-docs/a, /optional-docs/a/b 等。当匹配 /optional-docsparams.slugundefined

示例 (app router - Catch-all):

// app/files/[...filePaths]/page.tsx
export default function FileViewerPage({
params,
}: {
params: { filePaths: string[] };
}) {
// 如果 URL 是 /files/a/b/c.txt
// params.filePaths 将是 ['a', 'b', 'c.txt']
const path = params.filePaths.join("/");
return <div>文件路径: {path}</div>;
}

示例 (app router - Optional Catch-all):

// app/shop/[[...filters]]/page.tsx
export default function ShopPage({
params,
}: {
params: { filters?: string[] };
}) {
// URL: /shop -> params.filters is undefined
// URL: /shop/clothing/t-shirts -> params.filters is ['clothing', 't-shirts']
const filterString = params.filters ? params.filters.join(" / ") : "无";
return <div>当前筛选: {filterString}</div>;
}

路由分组 (Route Groups)

说明: app router 特有功能。使用圆括号 () 包裹文件夹名来创建路由组。路由组用于组织文件结构或将路由段从布局中排除,但不会影响 URL 路径。

示例: app/(marketing)/about/page.tsx -> /about app/(shop)/products/page.tsx -> /products 这允许 (marketing)(shop) 组拥有各自不同的根 layout.tsx,但 URL 保持简洁。

app/
├── (marketing)/ # Marketing 组
│ ├── layout.tsx # Marketing 布局
│ └── about/
│ └── page.tsx # /about 页面
├── (shop)/ # Shop 组
│ ├── layout.tsx # Shop 布局
│ └── products/
│ └── page.tsx # /products 页面
└── layout.tsx # 应用根布局 (可能被组布局覆盖)
└── page.tsx # 首页

平行路由 (Parallel Routes)

说明: app router 特有功能。允许在同一个布局中同时渲染一个或多个页面。适用于仪表板、分屏视图等场景。通过 @ 符号创建 "插槽" (Slots)。每个插槽是一个独立的页面,有自己的加载和错误状态。

示例: 假设需要在 dashboard 布局中同时显示 teamanalytics 两个视图。

app/
└── dashboard/
├── @analytics/ # Analytics 插槽
│ └── page.tsx # /dashboard (渲染在 analytics 插槽)
├── @team/ # Team 插槽
│ └── page.tsx # /dashboard (渲染在 team 插槽)
├── layout.tsx # Dashboard 布局,接收插槽作为 children props
└── page.tsx # /dashboard (主内容区,可选)
// app/dashboard/layout.tsx
export default function DashboardLayout({
children, // 主内容 (来自 page.tsx 或子路由)
analytics, // 对应 @analytics 插槽
team, // 对应 @team 插槽
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
<section>
<h1>Dashboard Layout</h1>
{children}
<div style={{ display: "flex", gap: "20px", marginTop: "20px" }}>
<div style={{ border: "1px solid blue", padding: "10px" }}>
<h2>分析视图</h2>
{analytics}
</div>
<div style={{ border: "1px solid green", padding: "10px" }}>
<h2>团队视图</h2>
{team}
</div>
</div>
</section>
);
}

2. 路由导航

说明: next/link 组件用于在 Next.js 应用的不同页面之间进行客户端导航。它会自动预取(prefetch)链接指向页面的代码,使得导航更快。

示例:

import Link from "next/link";

function Navigation() {
return (
<nav>
<ul>
<li>
<Link href="/">首页</Link>
</li>
<li>
<Link href="/about">关于我们</Link>
</li>
<li>
{/* 动态路由链接 */}
<Link href="/posts/my-first-post">第一篇文章</Link>
</li>
<li>
{/* 带查询参数的链接 */}
<Link href={{ pathname: "/search", query: { keyword: "nextjs" } }}>
搜索 Next.js
</Link>
</li>
<li>
{/* 替换当前历史记录项,而不是添加新的 */}
<Link href="/settings" replace>
设置
</Link>
</li>
<li>
{/* 禁用预取 */}
<Link href="/contact" prefetch={false}>
联系我们 (不预取)
</Link>
</li>
</ul>
</nav>
);
}

export default Navigation;

useRouter 钩子 (编程式导航)

说明: useRouter (来自 next/navigationnext/router) 提供了 pushreplace 方法,用于在 JavaScript 代码中触发导航。

示例 (app router):

"use client";

import { useRouter } from "next/navigation";

function LoginButton() {
const router = useRouter();

const handleLogin = async () => {
// 假设登录成功...
const success = true; // 模拟登录结果
if (success) {
// 导航到用户仪表盘页面
router.push("/dashboard");
// 如果不希望登录页出现在历史记录中,可以使用 replace
// router.replace('/dashboard');
} else {
alert("登录失败");
}
};

return <button onClick={handleLogin}>登录</button>;
}

路由拦截 (Intercepting Routes)

说明: app router 特有功能。允许在一个路由(如 Feed)中加载另一个路由(如 Photo Modal),同时 URL 显示后者的路径。当直接访问或刷新被拦截路由的 URL 时,会渲染其完整的页面,而不是在原始上下文中显示。使用 (.), (..), (..)(..), (...) 符号约定。

  • (.):匹配同级路由段。
  • (..):匹配上一级路由段。
  • (..)(..):匹配上两级路由段。
  • (...):从根 app 目录开始匹配。

示例: 假设有一个图片 Feed (/feed),点击图片时希望在 Feed 页面上方以 Modal 形式打开图片详情 (/photo/[id]),但 URL 变为 /photo/[id]

app/
├── feed/
│ ├── page.tsx # 显示图片列表,Link 指向 /photo/[id]
│ └── (..)photo/ # 拦截文件夹
│ └── [id]/
│ └── page.tsx # Modal 内容,拦截自 feed 上一级的 photo/[id]
├── photo/
│ └── [id]/
│ └── page.tsx # 图片详情的完整页面 (直接访问 /photo/[id] 时显示)
└── layout.tsx
// app/feed/page.tsx
import Link from "next/link";

export default function Feed() {
const photos = [{ id: "1" }, { id: "2" }]; // 示例数据
return (
<div>
<h1>图片 Feed</h1>
{photos.map((photo) => (
<Link key={photo.id} href={`/photo/${photo.id}`} scroll={false}>
<img
src={`/path/to/thumbnail/${photo.id}.jpg`}
alt={`Photo ${photo.id}`}
/>
</Link>
))}
</div>
);
}

// app/feed/(..)photo/[id]/page.tsx (Modal 内容)
export default function PhotoModal({ params }: { params: { id: string } }) {
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
background: "rgba(0,0,0,0.8)",
color: "white",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<h1>拦截到的图片 Modal: ID {params.id}</h1>
{/* 这里可以放图片 */}
<img
src={`/path/to/full/${params.id}.jpg`}
alt={`Photo ${params.id}`}
style={{ maxWidth: "80%", maxHeight: "80%" }}
/>
</div>
);
}

// app/photo/[id]/page.tsx (完整页面)
export default function PhotoPage({ params }: { params: { id: string } }) {
return (
<div>
<h1>完整图片页面: ID {params.id}</h1>
<img
src={`/path/to/full/${params.id}.jpg`}
alt={`Photo ${params.id}`}
style={{ width: "100%" }}
/>
</div>
);
}

中间件 (Middleware)

说明: 允许在请求完成之前运行代码。基于用户的请求,可以进行重写、重定向、添加或修改请求/响应头、或直接响应。中间件运行在 Edge Runtime。在项目根目录(或 src/下)创建 middleware.ts (或 .js) 文件。

示例 (认证检查):

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
const currentUserToken = request.cookies.get("sessionToken")?.value;
const { pathname } = request.nextUrl;

// 如果用户未登录且试图访问受保护的 /dashboard 路径
if (!currentUserToken && pathname.startsWith("/dashboard")) {
// 重定向到登录页面
return NextResponse.redirect(new URL("/login", request.url));
}

// 如果用户已登录且试图访问登录页面
if (currentUserToken && pathname === "/login") {
// 重定向到仪表盘
return NextResponse.redirect(new URL("/dashboard", request.url));
}

// 继续处理请求
return NextResponse.next();
}

// 配置 Matcher 指定中间件应用的路径
export const config = {
matcher: ["/dashboard/:path*", "/login"], // 只对这些路径运行中间件
};

重定向 (Redirects)

说明: 将一个路由路径永久或临时地指向另一个路径。可以在 next.config.js 中配置,也可以在 getServerSideProps (pages router)、Route Handlers (app router) 或 Middleware 中动态执行。

示例 (next.config.js):

// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/old-blog/:slug", // 源路径
destination: "/news/:slug", // 目标路径
permanent: true, // true: 308 永久重定向, false: 307 临时重定向
},
{
source: "/about-us",
destination: "/about",
permanent: false,
},
// 带参数匹配
{
source: "/user/:id(\\d+)", // 只匹配数字 ID
destination: "/profile/:id",
permanent: true,
},
];
},
};

示例 (Middleware 重定向): (见上方 Middleware 部分)

错误处理

说明: Next.js 提供了处理应用中错误的机制。

  • App Router:
    • error.tsx: 捕获嵌套路由段及其子组件的 运行时 错误,并渲染一个回退 UI。它必须是一个客户端组件 ('use client')。
    • global-error.tsx: 捕获根 layout.tsx 中的错误。
    • not-found.tsx: 当 notFound() 函数被调用或 URL 不匹配任何路由时渲染。
  • Pages Router:
    • pages/404.tsx: 自定义 404 页面。
    • pages/_error.tsx: 处理服务端和客户端的运行时错误 (如 500 错误)。不推荐用于 404 错误。

示例 (app router - error.tsx):

// app/dashboard/error.tsx
"use client"; // 错误组件必须是客户端组件

import { useEffect } from "react";

export default function DashboardError({
error, // 错误对象
reset, // 尝试重新渲染路由段的函数
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// 可以将错误记录到日志服务
console.error(error);
}, [error]);

return (
<div>
<h2>仪表盘出错了!</h2>
<p>{error.message}</p>
<button
onClick={
// 尝试恢复,重新渲染当前路由段
() => reset()
}
>
重试
</button>
</div>
);
}

示例 (app router - not-found.tsx):

// app/not-found.tsx (或在特定路由段内创建)
import Link from "next/link";

export default function NotFound() {
return (
<div>
<h2>页面未找到 (404)</h2>
<p>抱歉,我们找不到您请求的页面。</p>
<Link href="/">返回首页</Link>
</div>
);
}

3. 布局系统 (App Router)

App Router 引入了一套基于文件约定的布局系统,用于构建嵌套的、共享的 UI 结构。

layout.js / .tsx

说明: 定义路由段及其子路由段共享的 UI 界面。Layout 组件接收一个 children prop,用于渲染子布局或子页面。在导航时,Layout 组件会保留其状态,不会重新挂载。根布局必须包含 <html><body> 标签。

示例:

// app/dashboard/layout.tsx
import SideNav from "@/components/SideNav"; // 假设有一个侧边导航组件

export default function DashboardLayout({
children, // children 将是 page.tsx 或嵌套的 layout.tsx
}: {
children: React.ReactNode;
}) {
return (
<section style={{ display: "flex" }}>
<SideNav /> {/* 共享的侧边导航 */}
<main style={{ flexGrow: 1, padding: "20px" }}>
{children} {/* 渲染子页面或子布局 */}
</main>
</section>
);
}

// app/layout.tsx (根布局示例)
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
{/* 这里可以放置全局 Header, Footer 等 */}
{children}
</body>
</html>
);
}

template.js / .tsx

说明: 类似于 layout.js,也接收 children prop 并包裹子路由。主要区别在于:每次导航到其关联的路由段时,template 组件都会重新挂载,状态不会被保留。这对于需要执行进入/退出动画或在每次导航时重置某些状态/效果(如 useEffect)的场景很有用。

示例:

// app/some-section/template.tsx
"use client"; // 通常 template 用于执行副作用或动画,设为客户端组件

import { useEffect } from "react";

export default function SectionTemplate({
children,
}: {
children: React.ReactNode;
}) {
useEffect(() => {
// 每次进入此路由段时执行动画或逻辑
console.log("进入 SectionTemplate");
// 可以在 return 中执行清理逻辑 (退出时)
return () => {
console.log("退出 SectionTemplate");
};
}, []);

return <div>{children}</div>;
}

loading.js / .tsx

说明: 一个可选的文件,用于创建基于 React Suspense 的加载状态 UI。当路由段的内容(page.js 或子 layout.js)正在加载时,Next.js 会自动将它们包裹在 <Suspense> 中,并将 loading.js 导出的组件作为 fallback 显示。

示例:

// app/dashboard/loading.tsx
export default function DashboardLoading() {
// 你可以添加任何 React 组件,比如骨架屏
return (
<div>
<p>正在加载仪表盘数据...</p>
{/* 这里可以放一个 Spinner 或骨架屏组件 */}
</div>
);
}

error.js / .tsx

说明: 用于创建路由段级别的错误 UI 边界。当该路由段或其子组件在渲染过程中抛出运行时错误时,error.js 导出的组件会被渲染。它必须是一个客户端组件 ('use client'),并接收 error (错误对象) 和 reset (尝试重新渲染的函数) 两个 props。

示例: (已在上一部分提供,这里简洁展示结构)

// app/products/error.tsx
"use client"; // 错误组件必须是客户端组件

export default function ProductsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>加载产品出错!</h2>
<button onClick={() => reset()}>重试</button>
</div>
);
}

not-found.js / .tsx

说明: 用于渲染 404 未找到状态的 UI。当在 Server Component 中调用 notFound() 函数,或者用户访问了一个不存在的 URL 路径时,Next.js 会查找并渲染最近的 not-found.js 文件。

示例: (已在上一部分提供,这里简洁展示结构)

// app/not-found.tsx (根级别的 404)
import Link from "next/link";

export default function NotFound() {
return (
<div>
<h2>404 - 页面未找到</h2>
<Link href="/">返回首页</Link>
</div>
);
}

嵌套布局

说明: 布局是层级嵌套的。子路由段的 layout.js 会被父路由段的 layout.js 包裹。根布局 (app/layout.js) 包裹整个应用。

示例结构:

app/
├── layout.tsx # 根布局 (<html>, <body>)
├── page.tsx # 首页

└── dashboard/ # /dashboard 路由段
├── layout.tsx # Dashboard 布局 (例如带侧边栏)
├── page.tsx # /dashboard 页面 (会被 Dashboard 布局包裹)

└── settings/ # /dashboard/settings 路由段
└── page.tsx # /dashboard/settings 页面 (会被 Dashboard 布局包裹)
// app/dashboard/layout.tsx (父布局)
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="dashboard-container">
<aside>仪表盘侧边栏</aside>
<main>{children}</main> {/* children 是 settings/page.tsx */}
</div>
);
}

// app/dashboard/settings/page.tsx (子页面)
export default function SettingsPage() {
return <h1>设置页面</h1>;
}

三、数据处理

1. 状态管理

Next.js 本身不强制绑定特定的状态管理库,可以根据项目需求选择。

React Context

说明: React 内置的状态管理方案,适用于简单的、跨组件层级不深的状态共享。通常需要结合 'use client' 使用,因为 Context Provider 和 Consumer Hooks 需要在客户端运行。

示例:

// contexts/ThemeContext.tsx
"use client"; // Context Provider 通常需要是客户端组件

import React, { createContext, useState, useContext, ReactNode } from "react";

type ThemeContextType = {
theme: string;
toggleTheme: () => void;
};

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light");

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}

// app/layout.tsx (在根布局或需要共享的布局中使用 Provider)
import { ThemeProvider } from "@/contexts/ThemeContext";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
<ThemeProvider>
{" "}
{/* 包裹需要共享状态的部分 */}
{children}
</ThemeProvider>
</body>
</html>
);
}

// components/ThemeToggleButton.tsx (使用 Context)
("use client");

import { useTheme } from "@/contexts/ThemeContext";

export default function ThemeToggleButton() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
切换到 {theme === "light" ? "深色" : "浅色"} 主题
</button>
);
}

Redux 集成

说明: 适用于大型复杂应用的状态管理。需要在客户端设置 Provider 来包裹应用或部分组件树。通常会使用 @reduxjs/toolkit 来简化 Redux 配置。

示例 (概念性):

// store/store.ts (使用 @reduxjs/toolkit)
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counter/counterSlice"; // 假设有一个 counter slice

export const makeStore = () => {
return configureStore({
reducer: {
counter: counterReducer,
},
});
};

export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];

// components/StoreProvider.tsx (客户端组件包裹 Provider)
("use client");
import { useRef } from "react";
import { Provider } from "react-redux";
import { makeStore, AppStore } from "../store/store";

export default function StoreProvider({
children,
}: {
children: React.ReactNode;
}) {
const storeRef = useRef<AppStore | null>(null);
if (!storeRef.current) {
// 第一次渲染时创建 store
storeRef.current = makeStore();
}

return <Provider store={storeRef.current}>{children}</Provider>;
}

// app/layout.tsx (使用 Provider)
import StoreProvider from "@/components/StoreProvider";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
<StoreProvider>{children}</StoreProvider>
</body>
</html>
);
}

// components/Counter.tsx (使用 Redux state 和 dispatch)
("use client");
import { useSelector, useDispatch } from "react-redux";
import type { RootState, AppDispatch } from "../store/store";
import { increment, decrement } from "../store/features/counter/counterSlice"; // 假设的 actions

export function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch<AppDispatch>();

return (
<div>
<div>计数: {count}</div>
<button onClick={() => dispatch(increment())}>增加</button>
<button onClick={() => dispatch(decrement())}>减少</button>
</div>
);
}

Zustand

说明: 一个轻量、快速、可扩展的状态管理库,使用 Hooks API,样板代码少。状态存储在 React 组件树之外。

示例:

// store/bearsStore.ts
import { create } from "zustand";

type BearState = {
bears: number;
increasePopulation: () => void;
removeAllBears: () => void;
};

// 创建 store
const useBearStore = create<BearState>((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));

export default useBearStore;

// components/BearCounter.tsx (使用 store)
("use client");
import useBearStore from "@/store/bearsStore";

export default function BearCounter() {
// 直接在组件中使用 hook 获取状态
const bears = useBearStore((state) => state.bears);
return <h1>{bears} 只熊在附近</h1>;
}

// components/BearControls.tsx (使用 store actions)
("use client");
import useBearStore from "@/store/bearsStore";

export default function BearControls() {
// 获取 actions
const increasePopulation = useBearStore((state) => state.increasePopulation);
const removeAllBears = useBearStore((state) => state.removeAllBears);

return (
<>
<button onClick={increasePopulation}>增加熊</button>
<button onClick={removeAllBears}>移除所有熊</button>
</>
);
}

Jotai

说明: 一个原子化的状态管理库。状态由小的、独立的部分(原子)组成,组件只订阅它们需要的原子。

示例:

// store/atoms.ts
import { atom } from "jotai";

// 定义一个原子 (状态单元)
export const countAtom = atom(0); // 初始值为 0
export const textAtom = atom("hello");

// 派生原子 (基于其他原子计算)
export const uppercaseTextAtom = atom((get) => get(textAtom).toUpperCase());

// components/CounterDisplay.tsx (使用原子)
("use client");
import { useAtom } from "jotai";
import { countAtom } from "@/store/atoms";

export default function CounterDisplay() {
// 读取原子值
const [count] = useAtom(countAtom);
return <div>计数: {count}</div>;
}

// components/CounterControls.tsx (更新原子)
("use client");
import { useAtom } from "jotai";
import { countAtom } from "@/store/atoms";

export default function CounterControls() {
// useAtom 返回 [value, setValue]
const [, setCount] = useAtom(countAtom);

return <button onClick={() => setCount((c) => c + 1)}>增加</button>;
}

// components/TextInput.tsx (读写原子)
("use client");
import { useAtom } from "jotai";
import { textAtom, uppercaseTextAtom } from "@/store/atoms";

export default function TextInput() {
const [text, setText] = useAtom(textAtom);
const [uppercaseText] = useAtom(uppercaseTextAtom); // 只读派生原子

return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>输入内容: {text}</p>
<p>大写内容: {uppercaseText}</p>
</div>
);
}

// 注意:Jotai 的 Provider 是可选的,用于隔离状态或初始化。
// 如果不使用 Provider,状态是全局共享的。
// import { Provider } from 'jotai'
// <Provider> <Component /> </Provider>

React Query (TanStack Query)

说明: 主要用于管理服务端状态,包括数据获取、缓存、同步和更新。它极大地简化了与 API 交互的逻辑,提供了开箱即用的缓存、后台更新、请求重试等功能。通常在客户端组件中使用其 Hooks (useQuery, useMutation 等)。

示例:

// components/PostsList.tsx
"use client";
import {
useQuery,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; // 开发工具

// 1. 创建 QueryClient 实例 (通常在应用根部或布局中创建一次)
const queryClient = new QueryClient();

// 2. 定义数据获取函数
async function fetchPosts() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!res.ok) {
throw new Error("Network response was not ok");
}
return res.json();
}

function Posts() {
// 3. 使用 useQuery hook 获取、缓存和管理数据
const { data, error, isLoading, isFetching } = useQuery({
queryKey: ["posts"], // 缓存的唯一键
queryFn: fetchPosts, // 数据获取函数
staleTime: 5 * 60 * 1000, // 数据 5 分钟内被认为是新鲜的,不会重新获取
// cacheTime: 10 * 60 * 1000, // 数据在内存中保留 10 分钟 (默认 5 分钟)
});

if (isLoading) return <div>加载中...</div>;
if (error) return <div>发生错误: {error.message}</div>;

return (
<div>
<h1>文章列表 {isFetching ? "(后台更新中...)" : ""}</h1>
<ul>
{data?.slice(0, 10).map(
(
post: any // 假设 post 有 id 和 title
) => (
<li key={post.id}>{post.title}</li>
)
)}
</ul>
</div>
);
}

// 4. 在应用或布局中包裹 QueryClientProvider
export default function PostsListWrapper() {
return (
<QueryClientProvider client={queryClient}>
<Posts />
<ReactQueryDevtools initialIsOpen={false} /> {/* 可选的开发工具 */}
</QueryClientProvider>
);
}

2. 数据缓存 (App Router)

Next.js App Router 提供了强大的内置数据缓存机制,尤其是在 Server Components 中使用 fetch 时。

fetch 缓存机制

说明: Next.js 扩展了原生的 fetch API,并默认启用智能缓存。

  • 默认行为 (cache: 'force-cache'): fetch 的结果会被尽可能地缓存。如果多次请求相同的 URL,会直接使用缓存数据,这有助于实现 SSG 和快速响应。
  • 不缓存 (cache: 'no-store'): 每次请求都会重新获取数据,类似于 getServerSideProps 的行为,适用于需要实时数据的动态渲染。
  • ISR (next: { revalidate: number }): 缓存数据,但在指定秒数后,下次请求会触发后台重新验证(重新获取数据)。如果数据更新,缓存会更新,否则旧缓存继续生效。

示例: (已在第一部分数据获取中提供,此处不再重复)

缓存标签 (Cache Tags)

说明: 允许你为 fetch 请求添加标签,然后可以按需、批量地使带有特定标签的缓存失效(重新验证)。这对于需要手动触发缓存更新的场景非常有用(例如,CMS 内容更新后)。

示例:

// app/posts/page.tsx (获取数据并打标签)
async function getPosts() {
const res = await fetch("https://.../posts", {
next: { tags: ["posts", "collection"] }, // 为这个 fetch 请求打上标签
});
return res.json();
}

export default async function PostsPage() {
const posts = await getPosts();
// ... 渲染 posts
}

// app/api/revalidate/route.ts (按标签重新验证的 API 路由)
import { NextRequest, NextResponse } from "next/server";
import { revalidateTag } from "next/cache";

export async function POST(request: NextRequest) {
// 在实际应用中,这里应该有权限校验
const tag = await request.json().then((body) => body.tag);

if (!tag) {
return NextResponse.json(
{ error: "Missing tag parameter" },
{ status: 400 }
);
}

try {
// 使带有指定标签的所有 fetch 缓存失效
revalidateTag(tag);
console.log(`成功重新验证标签: ${tag}`);
return NextResponse.json({ revalidated: true, tag: tag, now: Date.now() });
} catch (err) {
console.error(`重新验证标签 ${tag} 失败:`, err);
return NextResponse.json(
{ error: "Revalidation failed", tag: tag },
{ status: 500 }
);
}
}

// 调用示例:
// POST /api/revalidate
// Body: { "tag": "posts" }
// 这会使上面 getPosts() 的缓存失效

重新验证策略 (Revalidation Strategies)

说明: 主要有两种策略使缓存数据保持最新:

  1. 基于时间的重新验证 (Time-based Revalidation - ISR): 通过 fetchnext.revalidate 选项或页面级别的 export const revalidate = number 来设置固定的重新验证间隔。
  2. 按需重新验证 (On-Demand Revalidation): 通过 revalidateTag(tag)revalidatePath(path) 函数手动触发特定缓存的重新验证。通常在数据变更后(如 CMS 更新、数据库写入)调用。

示例: (ISR 见上文渲染模式,按需见 revalidateTag 和下方 revalidatePath 示例)

强制重新验证 (On-Demand Revalidation)

说明: 指按需重新验证。除了 revalidateTag,还可以使用 revalidatePath 来重新验证特定 URL 路径关联的数据缓存和页面渲染。

示例:

// app/api/revalidate-path/route.ts (按路径重新验证)
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";

export async function POST(request: NextRequest) {
// 权限校验...
const path = await request.json().then((body) => body.path);

if (!path) {
return NextResponse.json(
{ error: "Missing path parameter" },
{ status: 400 }
);
}

try {
// 使指定路径关联的缓存失效并触发重新生成
// 第三个参数 'page' 表示重新生成页面, 'layout' 表示重新生成布局
revalidatePath(path, "page");
console.log(`成功重新验证路径: ${path}`);
return NextResponse.json({
revalidated: true,
path: path,
now: Date.now(),
});
} catch (err) {
console.error(`重新验证路径 ${path} 失败:`, err);
return NextResponse.json(
{ error: "Revalidation failed", path: path },
{ status: 500 }
);
}
}

// 调用示例:
// POST /api/revalidate-path
// Body: { "path": "/posts/my-updated-post" }

缓存持久化

说明: Next.js 的内置数据缓存(Data Cache)默认是持久化的,它存储在服务器的文件系统上(或根据部署环境可能不同)。这意味着即使服务器重启或创建新实例(在某些部署策略下),只要缓存未过期或未被手动重新验证,它仍然可能被复用。然而,跨部署(当你推送新代码时)通常会导致缓存被清除。Vercel 等平台提供了更精细的缓存管理。

3. API 集成

REST API

说明: 使用 fetch (Server Components 或 Client Components) 或客户端库 (如 axios, swr, react-query 在 Client Components 中) 与 RESTful API 进行交互。

示例 (Server Component):

// app/users/[id]/page.tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
// cache: 'no-store', // 如果需要实时数据
// next: { revalidate: 60 } // 如果需要 ISR
});
if (!res.ok) throw new Error("Failed to fetch user");
return res.json();
}

export default async function UserProfile({
params,
}: {
params: { id: string };
}) {
const user = await getUser(params.id);
return <div>用户名: {user.name}</div>;
}

GraphQL

说明: 可以通过 fetch 发送 POST 请求来与 GraphQL 端点交互,或者使用 Apollo Client、urql 等专门的 GraphQL 客户端库(通常在 Client Components 中设置)。

示例 (使用 fetch):

// lib/graphqlClient.ts
export async function fetchGraphQL(
query: string,
variables?: Record<string, any>
) {
const response = await fetch("https://api.example.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
// 'Authorization': 'Bearer YOUR_TOKEN', // 如果需要认证
},
body: JSON.stringify({ query, variables }),
// cache / next.revalidate 选项同样适用于 GraphQL fetch
// cache: 'no-store'
});

if (!response.ok) {
console.error("GraphQL fetch error:", response.status, response.statusText);
throw new Error("Failed to fetch GraphQL API");
}

const result = await response.json();
if (result.errors) {
console.error("GraphQL errors:", result.errors);
throw new Error("GraphQL query returned errors");
}
return result.data;
}

// app/some-data/page.tsx (Server Component)
import { fetchGraphQL } from "@/lib/graphqlClient";

const GET_ITEM_QUERY = `
query GetItem($id: ID!) {
item(id: $id) {
id
name
description
}
}
`;

export default async function SomeDataPage() {
// 实际 ID 应动态获取
const data = await fetchGraphQL(GET_ITEM_QUERY, { id: "item-123" });
const item = data.item;

return (
<div>
<h1>{item.name}</h1>
<p>{item.description}</p>
</div>
);
}

tRPC

说明: 允许你构建端到端类型安全的 API,无需代码生成或模式定义。客户端可以直接调用后端函数,并获得完整的 TypeScript 类型提示和自动补全。需要设置 tRPC Router (后端) 和 tRPC Client (前端)。

示例 (概念性 - 实际设置较复杂):

// server/trpc/routers/appRouter.ts (后端 Router 定义)
import { initTRPC } from "@trpc/server";
import { z } from "zod"; // 用于输入校验

const t = initTRPC.create();

export const appRouter = t.router({
getUser: t.procedure
.input(z.object({ userId: z.string() })) // 输入校验
.query(async ({ input }) => {
// 假设这是获取用户的逻辑
const user = { id: input.userId, name: `User ${input.userId}` };
return user;
}),
createUser: t.procedure
.input(z.object({ name: z.string() }))
.mutation(async ({ input }) => {
// 创建用户的逻辑...
console.log(`创建用户: ${input.name}`);
return { success: true, name: input.name };
}),
});

export type AppRouter = typeof appRouter; // 导出类型供前端使用

// lib/trpc/client.ts (前端 tRPC Client 设置 - 通常在 Client Component 使用)
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "@/server/trpc/routers/appRouter";

// 创建 tRPC React Query 客户端
export const trpc = createTRPCReact<AppRouter>();

// utils/trpc-provider.tsx (包裹应用的 Provider)
("use client");
import React, { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { trpc } from "./client"; // 你的 trpc client

export default function TRPCProvider({
children,
}: {
children: React.ReactNode;
}) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: "/api/trpc", // 指向你的 tRPC API 路由
}),
],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
);
}

// app/layout.tsx (使用 Provider)
import TRPCProvider from "@/utils/trpc-provider";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<TRPCProvider>{children}</TRPCProvider>
</body>
</html>
);
}

// components/UserInfo.tsx (客户端组件调用 tRPC)
("use client");
import { trpc } from "@/lib/trpc/client";

export default function UserInfo({ userId }: { userId: string }) {
// 使用 tRPC hook 调用后端 query (类似 React Query)
const { data: user, isLoading, error } = trpc.getUser.useQuery({ userId });

if (isLoading) return <p>加载用户信息...</p>;
if (error) return <p>错误: {error.message}</p>;

return <div>用户名: {user?.name}</div>;
}

API 路由处理 (Route Handlers / API Routes)

说明: Next.js 内建的创建后端 API 端点的方式。

  • App Router: 在 app/api/... 目录下创建 route.ts (或 .js) 文件。
  • Pages Router: 在 pages/api/... 目录下创建 .ts.js 文件。 用于处理来自客户端或其他服务的请求,执行服务器端逻辑,如数据库操作、调用外部服务等。

示例: (见第一部分基础概念中的 API Routes / Route Handlers 示例)

外部 API 集成

说明: 指在 Next.js 应用中调用第三方或公司内部的其他 API。关键在于使用 fetch 或相应的库,并安全地管理 API 密钥/认证凭证(通常通过环境变量)。

示例 (使用环境变量):

// .env.local (不要提交到 Git)
EXTERNAL_API_KEY = "your_secret_api_key";
EXTERNAL_API_ENDPOINT = "https://api.thirdparty.com/v1";

// next.config.js (使环境变量在浏览器端可用 - 如果需要)
// 注意:服务端组件可以直接访问 process.env
// const nextConfig = {
// env: {
// NEXT_PUBLIC_API_ENDPOINT: process.env.EXTERNAL_API_ENDPOINT, // 以 NEXT_PUBLIC_ 开头
// },
// };
// module.exports = nextConfig;

// lib/externalApi.ts (服务端调用示例)
const apiKey = process.env.EXTERNAL_API_KEY;
const apiEndpoint = process.env.EXTERNAL_API_ENDPOINT;

export async function fetchExternalData(resource: string) {
if (!apiKey || !apiEndpoint) {
throw new Error("API key or endpoint not configured");
}
const response = await fetch(`${apiEndpoint}/${resource}`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
// cache: 'no-store' // 根据需要设置缓存策略
});
if (!response.ok) {
throw new Error(`Failed to fetch external API: ${resource}`);
}
return response.json();
}

// app/external-data/page.tsx (在 Server Component 中使用)
import { fetchExternalData } from "@/lib/externalApi";

export default async function ExternalDataPage() {
try {
const data = await fetchExternalData("some-resource");
return <pre>{JSON.stringify(data, null, 2)}</pre>;
} catch (error: any) {
return <p>获取外部数据失败: {error.message}</p>;
}
}

中间件 (Middleware)

说明: 中间件可以在请求到达 API 路由或页面之前运行代码。可用于 API 请求的认证、授权、日志记录、重定向、重写请求头等。

示例 (API 认证):

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;

// 只对 /api/secure/* 路径下的 API 应用认证检查
if (pathname.startsWith("/api/secure")) {
const authToken = request.headers.get("Authorization");
const isValidToken = await validateToken(authToken); // 假设这是一个验证 token 的函数

if (!isValidToken) {
// 返回 401 未授权响应
return new NextResponse(
JSON.stringify({ success: false, message: "认证失败" }),
{ status: 401, headers: { "content-type": "application/json" } }
);
}
}

// 继续处理请求
return NextResponse.next();
}

// 假设的 token 验证函数
async function validateToken(token: string | null): Promise<boolean> {
// 在实际应用中,这里会解码、验证 JWT 或查询数据库等
return token === "Bearer valid-token";
}

export const config = {
matcher: "/api/secure/:path*", // 应用中间件的路径
};

四、性能优化

1. 图片优化

Image 组件 (next/image)

说明: Next.js 提供的内置组件,用于自动优化本地或远程图片。它提供了尺寸调整、格式转换 (WebP/AVIF)、懒加载、防止布局偏移 (CLS) 等功能。

示例:

import Image from "next/image";
import localImage from "../public/images/my-image.jpg"; // 导入本地图片

function MyComponent() {
return (
<div>
{/* 本地图片 (需要导入或放在 public) */}
{/* width 和 height 提供了图片原始尺寸,用于防止布局偏移 */}
<Image
src={localImage}
alt="我的本地图片"
width={500} // 图片原始宽度
height={300} // 图片原始高度
// sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" // 可选,用于响应式
// priority // 可选,用于 LCP 图片,禁用懒加载并优先加载
/>

{/* 远程图片 (需要在 next.config.js 中配置域名) */}
<Image
src="https://images.unsplash.com/photo-12345"
alt="来自 Unsplash 的图片"
width={800}
height={600}
// quality={75} // 可选,图片质量 (1-100)
/>

{/* 填充容器的图片 (需要父容器设置 position: relative 和尺寸) */}
<div style={{ position: "relative", width: "100%", height: "400px" }}>
<Image
src="/images/another-image.png" // public 目录下的图片
alt="填充容器的图片"
fill // 填充模式
style={{ objectFit: "cover" }} // 控制图片如何适应容器
/>
</div>
</div>
);
}

// next.config.js (配置远程图片域名)
// const nextConfig = {
// images: {
// remotePatterns: [
// {
// protocol: 'https',
// hostname: 'images.unsplash.com',
// // port: '', // 如果需要指定端口
// // pathname: '/account123/**', // 如果需要限制路径
// },
// ],
// },
// };
// module.exports = nextConfig;

自动图片优化

说明: Next.js 在请求时自动优化图片。它会根据 width, height, sizes 和设备特性生成合适尺寸和格式的图片,并进行缓存。

响应式图片

说明: 使用 sizes 属性结合 width/heightfill 模式,可以告知浏览器在不同视口宽度下图片预期会占据多大空间,从而让 Next.js 提供最优尺寸的图片源。

示例 (sizes 属性): (见上方 Image 组件示例)

图片格式转换

说明: 如果浏览器支持,Next.js 会自动将图片转换为更现代、更高效的格式,如 WebP 或 AVIF,即使原始图片是 JPEG 或 PNG。

延迟加载 (Lazy Loading)

说明: 默认情况下,next/image 会对图片启用懒加载 (loading="lazy"),意味着图片只在即将进入视口时才开始加载。对于首屏关键图片(LCP),应添加 priority 属性来禁用懒加载并优先加载。

示例 (priority 属性): (见上方 Image 组件示例)

2. 代码优化

代码分割 (Code Splitting)

说明: Next.js 自动进行基于页面的代码分割。每个页面只加载其运行所需的 JavaScript 代码。这减少了初始加载的包体积。

动态导入 (next/dynamic)

说明: 允许你手动将组件或模块标记为动态加载。这些代码会被分离到单独的 chunk 中,只在组件实际需要渲染时才加载。常用于优化大型组件、第三方库或仅在特定条件下显示的内容。

示例: (见第一部分基础概念中的动态导入示例)

预加载 (Prefetching)

说明: next/link 组件默认会在链接进入视口或被悬停时,在后台预取目标页面所需的 JavaScript 代码。这使得后续导航几乎是瞬时的。可以通过 prefetch={false} 禁用此行为。

示例: (见第一部分路由系统中的 Link 组件示例)

Tree Shaking

说明: 一个在打包过程中移除 JavaScript 上下文中未引用代码(死代码)的过程。Next.js 使用的打包工具(Webpack/Turbopack)支持 Tree Shaking。编写模块化、使用 ES Modules 语法的代码有助于提高 Tree Shaking 的效率。

打包优化 (Bundle Optimization)

说明: 分析和优化最终生成的 JavaScript 包的大小和组成。可以使用 @next/bundle-analyzer 工具来可视化分析哪些模块占用了包体积。

示例 (配置 @next/bundle-analyzer):

npm install --save-dev @next/bundle-analyzer cross-env
# 或者
yarn add --dev @next/bundle-analyzer cross-env
pnpm add --save-dev @next/bundle-analyzer cross-env
// next.config.js
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true", // 仅在 ANALYZE=true 时启用
});

/** @type {import('next').NextConfig} */
const nextConfig = {
// ... 你的其他 Next.js 配置
reactStrictMode: true,
};

module.exports = withBundleAnalyzer(nextConfig);
// package.json (添加 scripts)
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"analyze": "cross-env ANALYZE=true next build" // 添加分析脚本
}
}

运行 npm run analyze (或 yarn analyze / pnpm analyze) 后,会在 .next/analyze/ 目录下生成 HTML 报告。

3. 性能监控

Core Web Vitals (CWV)

说明: Google 定义的一组关键用户体验指标,包括:

  • LCP (Largest Contentful Paint): 最大内容绘制时间,衡量加载性能。
  • FID (First Input Delay) / INP (Interaction to Next Paint): 首次输入延迟/下次绘制交互,衡量交互响应性。INP 是将取代 FID 的新指标。
  • CLS (Cumulative Layout Shift): 累积布局偏移,衡量视觉稳定性。

Next.js 可以通过 reportWebVitals 函数(在 pages/_app.js 或通过 Client Component 在 app/layout.js 中引入)来收集和上报这些指标。

示例 (app router - 在 Client Component 中使用 Hook):

// components/WebVitalsReporter.tsx
"use client";

import { useReportWebVitals } from "next/web-vitals";

export function WebVitalsReporter() {
useReportWebVitals((metric) => {
// metric 对象包含 name, value, id 等信息
console.log(metric); // 在控制台打印指标

// 这里可以将指标发送到你的分析服务
// sendToAnalytics(metric);

// 示例:根据指标类型做不同处理
switch (metric.name) {
case "FCP":
// 处理首次内容绘制 (First Contentful Paint)
break;
case "LCP":
// 处理最大内容绘制
break;
case "CLS":
// 处理累积布局偏移
break;
case "FID":
// 处理首次输入延迟
break;
case "INP":
// 处理下次绘制交互
break;
case "TTFB":
// 处理首字节时间 (Time to First Byte)
break;
// Next.js 特有指标
case "Next.js-hydration":
// 处理 Hydration 时间
break;
case "Next.js-route-change-to-render":
// 处理路由变化到渲染时间
break;
case "Next.js-render":
// 处理渲染时间
break;
default:
break;
}
});

return null; // 这个组件不渲染任何 UI
}

// app/layout.tsx (引入 Reporter)
import { WebVitalsReporter } from "@/components/WebVitalsReporter";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
{children}
<WebVitalsReporter /> {/* 添加 Web Vitals 报告器 */}
</body>
</html>
);
}

性能指标 (Performance Metrics)

说明: 除了 CWV,还有其他重要的性能指标,如 FCP (First Contentful Paint)、TTFB (Time to First Byte)、Hydration Time 等。reportWebVitals 可以捕获其中的一部分。

性能分析工具 (Profiling Tools)

说明:

  • 浏览器开发者工具: Lighthouse (审计性能、可访问性等)、Performance (录制和分析运行时性能)、Network (分析网络请求)。
  • React 开发者工具: Profiler (分析组件渲染时间和提交阶段)。
  • Next.js 构建输出: next build 命令会输出每个页面的大小信息。
  • @next/bundle-analyzer: 分析包构成。

性能优化策略 (Optimization Strategies)

说明: 这是一个综合性的概念,涉及之前讨论的多个方面:

  • 选择合适的渲染策略 (SSR, SSG, ISR, CSR)。
  • 优化图片 (next/image)。
  • 代码分割和动态导入。
  • 优化数据获取和缓存。
  • 减少 JavaScript 包大小,移除无用代码 (Tree Shaking)。
  • 优化字体加载。
  • 使用 Web Workers 处理耗时任务(如果适用)。
  • 减少第三方脚本的影响。

监控系统集成 (Monitoring System Integration)

说明: 将收集到的性能指标 (如 CWV) 发送到专门的监控或分析平台,以便长期跟踪、分析趋势和设置警报。常见的平台包括 Vercel Analytics (与 Next.js/Vercel 集成良好)、Google Analytics、Sentry、Datadog、New Relic 等。

示例 (在 reportWebVitals 中发送数据):

// components/WebVitalsReporter.tsx (续)
"use client";

import { useReportWebVitals } from "next/web-vitals";

function sendToAnalytics(metric: any) {
// 示例:发送到 Google Analytics 4
// if (window.gtag) {
// window.gtag('event', metric.name, {
// value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value), // CLS 值通常较小,乘以 1000
// event_label: metric.id, // 唯一 ID,区分同页面多次测量
// non_interaction: true, // 设为 true,避免影响跳出率
// });
// }

// 示例:发送到自定义的后端端点
const body = JSON.stringify({ [metric.name]: metric.value });
const url = "/api/analytics"; // 假设有一个 API 路由接收数据

// 使用 navigator.sendBeacon 发送数据 (更可靠,尤其在页面卸载时)
if (navigator.sendBeacon) {
navigator.sendBeacon(url, body);
} else {
// 回退到 fetch (可能在页面卸载时丢失)
fetch(url, { body, method: "POST", keepalive: true });
}

console.log(`已发送指标 ${metric.name}: ${metric.value}${url}`);
}

export function WebVitalsReporter() {
useReportWebVitals((metric) => {
sendToAnalytics(metric);
});
return null;
}

// 注意: 你需要自己实现 /api/analytics 这个 API 路由来接收和处理数据。

五、样式处理

Next.js 提供了多种方式来处理组件和页面的样式。

1. CSS 方案

CSS Modules

说明: Next.js 内置支持 CSS Modules。通过将 CSS 文件命名为 [name].module.css,可以实现样式的局部作用域,避免全局命名冲突。导入后,类名会生成唯一的哈希值。

示例:

/* components/Button.module.css */
.button {
padding: 10px 20px;
background-color: blue;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}

.button:hover {
background-color: darkblue;
}
// components/Button.tsx
import styles from "./Button.module.css"; // 导入 CSS Module

type ButtonProps = {
children: React.ReactNode;
onClick?: () => void;
};

export default function Button({ children, onClick }: ButtonProps) {
return (
<button className={styles.button} onClick={onClick}>
{" "}
{/* 使用导入的样式 */}
{children}
</button>
);
}

Sass/SCSS

说明: Next.js 内置支持 Sass/SCSS。需要先安装 sass 包 (npm install sass)。可以直接导入 .scss.sass 文件,也可以结合 CSS Modules 使用([name].module.scss)。支持变量、嵌套、混合等高级特性。

示例:

/* styles/variables.module.scss */
$primary-color: #8a2be2; // 定义 Sass 变量

.text {
color: $primary-color; // 使用 Sass 变量
}
/* styles/global.scss (全局样式) */
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"); // 导入字体

body {
font-family: "Roboto", sans-serif;
margin: 0;
padding: 0;
box-sizing: border-box;
}

a {
text-decoration: none;
color: inherit;
}
// app/layout.tsx (App Router - 引入全局样式)
import "@/styles/global.scss"; // 引入全局 SCSS 文件
import styles from "@/styles/variables.module.scss"; // 引入 SCSS Module

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
<h1 className={styles.text}>网站标题 (SCSS Module 样式)</h1>
{children}
</body>
</html>
);
}

// pages/_app.tsx (Pages Router - 引入全局样式)
// import '@/styles/global.scss';
// import type { AppProps } from 'next/app';
//
// export default function MyApp({ Component, pageProps }: AppProps) {
// return <Component {...pageProps} />;
// }

Tailwind CSS

说明: 一个流行的原子化/功能类优先的 CSS 框架。需要在 Next.js 项目中进行配置(安装依赖 tailwindcss postcss autoprefixer,创建 tailwind.config.jspostcss.config.js)。通过组合预定义的类名来快速构建 UI。

示例:

  1. 安装: npm install -D tailwindcss postcss autoprefixer

  2. 初始化: npx tailwindcss init -p

  3. 配置 tailwind.config.js:

    /** @type {import('tailwindcss').Config} */
    module.exports = {
    content: [
    // 指定 Tailwind 需要扫描的文件
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    // 或者对于 src 目录:
    // "./src/**/*.{js,ts,jsx,tsx,mdx}",
    ],
    theme: {
    extend: {}, // 可选:扩展默认主题
    },
    plugins: [],
    };
  4. 配置 globals.css (或你的全局 CSS 文件):

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

    /* 你可以添加自定义的 base 样式 */
    /* body { ... } */
  5. 在组件中使用:

    // components/Alert.tsx
    export default function Alert({ message }: { message: string }) {
    return (
    <div
    className="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 rounded shadow-md mb-4"
    role="alert"
    >
    {/* 使用 Tailwind 的功能类 */}
    <p className="font-bold">信息</p>
    <p>{message}</p>
    </div>
    );
    }

CSS-in-JS

说明: 允许在 JavaScript 或 TypeScript 文件中编写 CSS 样式的库。常见的库有 styled-componentsEmotion。它们提供了组件级的样式封装、动态样式和主题化等能力。在 Next.js 中使用时,特别是 App Router 的 Server Components,需要注意配置以确保证服务端渲染 (SSR) 和样式抽取正常工作。

styled-components

说明: 一个流行的 CSS-in-JS 库。通过创建带有附加样式的 React 组件来工作。在 Next.js 中使用需要额外设置,尤其是在 App Router 中,以处理服务端渲染和客户端注入。通常需要使用一个 "Registry" 组件来收集样式。

示例 (基本用法):

// components/StyledButton.tsx
"use client"; // styled-components 主要用于客户端组件

import styled from "styled-components";

// 创建一个带样式的 button 组件
const Button = styled.button`
background-color: ${(props) => (props.primary ? "palevioletred" : "white")};
color: ${(props) => (props.primary ? "white" : "palevioletred")};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
cursor: pointer;

&:hover {
opacity: 0.9;
}
`;

export default function StyledButton({
primary,
children,
}: {
primary?: boolean;
children: React.ReactNode;
}) {
return <Button primary={primary}>{children}</Button>;
}

注意: 要在 Next.js (尤其是 App Router) 中正确使用 styled-components 进行 SSR,需要遵循其官方文档的设置指南,通常涉及创建一个 StyledComponentsRegistry 来收集和注入样式。

全局样式

说明: 应用于整个应用的 CSS 样式。通常用于设置基础样式、CSS Reset、字体、全局变量等。

  • App Router: 在根 app/layout.js (或 .tsx) 文件中直接 import 全局 CSS 文件 (如 import '@/styles/globals.css')。
  • Pages Router: 在 pages/_app.js (或 .tsx) 文件中 import 全局 CSS 文件。

示例: (见上方 Sass/SCSS 和 Tailwind CSS 示例中的 globals.cssglobal.scss 的引入方式)

2. 主题系统

暗黑模式 (Dark Mode)

说明: 提供浅色和深色两种视觉主题。通常通过切换顶层元素(如 <html><body>)上的 classdata-theme 属性,并结合 CSS 变量来实现。可以使用 React Context 或状态管理库来管理当前主题状态,并提供切换功能。next-themes 是一个流行的简化此过程的库。

示例 (使用 CSS 变量和 data-theme):

/* styles/theme.css */
:root {
/* 浅色主题变量 (默认) */
--background-color: #ffffff;
--text-color: #333333;
--primary-color: #0070f3;
}

[data-theme="dark"] {
/* 深色主题变量 */
--background-color: #1a1a1a;
--text-color: #ffffff;
--primary-color: #3b82f6; /* 示例:深色模式下的蓝色可能需要调整 */
}

body {
background-color: var(--background-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}

button {
background-color: var(--primary-color);
color: var(--background-color); /* 假设按钮文字颜色与背景形成对比 */
/* 其他样式 */
}
// components/ThemeSwitcher.tsx
"use client";
import { useState, useEffect } from "react";

export default function ThemeSwitcher() {
const [theme, setTheme] = useState("light"); // 默认浅色

useEffect(() => {
// 组件加载时应用初始主题
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);

const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
document.documentElement.setAttribute("data-theme", newTheme); // 更新 HTML 属性
};

return (
<button onClick={toggleTheme}>
切换到 {theme === "light" ? "深色" : "浅色"} 模式
</button>
);
}

// 在布局文件中引入 theme.css 和 ThemeSwitcher 组件
// app/layout.tsx
// import '@/styles/theme.css';
// import ThemeSwitcher from '@/components/ThemeSwitcher';
// ...
// <body> <ThemeSwitcher /> {children} </body>

主题切换 (Theme Switching)

说明: 比暗黑模式更通用,允许用户在多个预定义的主题(如不同的颜色组合、字体)之间切换。实现方式与暗黑模式类似,通过更新 data-theme 属性或 CSS 类,并定义好每个主题对应的 CSS 变量或样式规则。

动态主题 (Dynamic Theming)

说明: 允许用户自定义主题颜色或基于某些数据动态生成主题。通常通过 JavaScript 在运行时修改 CSS 变量的值来实现。

示例:

// components/ColorPicker.tsx
"use client";
import { useState } from "react";

export default function ColorPicker() {
const [primaryColor, setPrimaryColor] = useState("#0070f3"); // 默认颜色

const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newColor = event.target.value;
setPrimaryColor(newColor);
// 动态更新 CSS 变量 --primary-color 的值
document.documentElement.style.setProperty("--primary-color", newColor);
};

return (
<div>
<label htmlFor="colorPicker">选择主颜色: </label>
<input
type="color"
id="colorPicker"
value={primaryColor}
onChange={handleColorChange}
/>
</div>
);
}

响应式设计 (Responsive Design)

说明: 确保应用在不同屏幕尺寸(桌面、平板、手机)上都有良好的布局和可用性。主要通过 CSS 媒体查询 (@media) 来实现。Tailwind CSS 等框架内置了响应式修饰符(如 sm:, md:, lg:)来简化响应式类的应用。

示例 (CSS 媒体查询):

/* styles/responsive.css */
.container {
width: 90%;
margin: 0 auto;
}

.grid {
display: grid;
grid-template-columns: 1fr; /* 移动端默认单列 */
gap: 1rem;
}

/* 平板及以上设备 (假设断点为 768px) */
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr); /* 两列 */
}
}

/* 桌面设备 (假设断点为 1024px) */
@media (min-width: 1024px) {
.container {
width: 80%;
max-width: 1200px;
}
.grid {
grid-template-columns: repeat(3, 1fr); /* 三列 */
}
}

样式变量 (Style Variables)

说明: 使用 CSS 自定义属性(CSS Variables)或 Sass/Less 变量来定义可重用的值(如颜色、字体大小、间距)。这使得样式更易于维护,并且是实现主题化的基础。

示例 (CSS 变量): (见上方 暗黑模式 示例)

示例 (Sass 变量): (见上方 Sass/SCSS 示例)

六、安全性

确保 Next.js 应用的安全性至关重要。

1. 认证 (Authentication)

验证用户身份的过程。

NextAuth.js

说明: 一个为 Next.js 量身打造的、功能齐全的开源认证库。极大地简化了添加各种认证方式(如 OAuth、邮箱/密码、Magic Link、Credentials)和会话管理的复杂度。支持 JWT 和数据库会话,提供方便的 Hooks (useSession) 和 API 获取会话状态。

示例 (基本配置):

// pages/api/auth/[...nextauth].js (Pages Router)
// 或 app/api/auth/[...nextauth]/route.ts (App Router)

import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
// import { PrismaAdapter } from "@next-auth/prisma-adapter"; // 如果使用数据库会话
// import prisma from "@/lib/prisma"; // 你的 Prisma client

export const authOptions = {
// adapter: PrismaAdapter(prisma), // 可选:配置数据库 Adapter
providers: [
GithubProvider({
// 配置 GitHub OAuth Provider
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
CredentialsProvider({
// 配置用户名/密码认证 Provider
name: "Credentials",
credentials: {
username: { label: "用户名", type: "text", placeholder: "jsmith" },
password: { label: "密码", type: "password" },
},
async authorize(credentials, req) {
// 在这里添加你的用户查找和密码验证逻辑
// 例如: const user = await findUserByUsername(credentials.username);
// if (user && await verifyPassword(credentials.password, user.passwordHash)) {
// return user; // 返回用户对象表示认证成功
// } else {
// return null; // 返回 null 表示认证失败
// }
// 模拟验证
if (
credentials?.username === "admin" &&
credentials?.password === "password"
) {
return { id: "1", name: "Admin User", email: "admin@example.com" }; // 确保返回的对象有 id
}
return null;
},
}),
// ...可以添加更多 providers
],
session: {
strategy: "jwt", // 或 "database"
},
secret: process.env.NEXTAUTH_SECRET, // 用于签名 JWT 或加密会话 cookie
// pages: { // 可选: 自定义页面路径
// signIn: '/auth/signin',
// signOut: '/auth/signout',
// error: '/auth/error', // Error code passed in query string as ?error=
// verifyRequest: '/auth/verify-request', // (used for email/magic link verification)
// newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
// }
// callbacks: { // 可选: 自定义回调行为
// async jwt({ token, user }) { ... },
// async session({ session, token, user }) { ... },
// }
};

// --- App Router ---
// import { handlers } from "@/auth" // Assuming auth.ts exports { handlers: { GET, POST }, auth }
// export const { GET, POST } = handlers

// --- Pages Router ---
// export default NextAuth(authOptions);

// --- App Router (app/api/auth/[...nextauth]/route.ts) ---
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

// --- 在布局中包裹 SessionProvider (通常在客户端布局或根布局的客户端部分) ---
// components/Providers.tsx
// 'use client';
// import { SessionProvider } from "next-auth/react";
//
// export default function Providers({ children }: { children: React.ReactNode }) {
// return <SessionProvider>{children}</SessionProvider>;
// }

// app/layout.tsx
// import Providers from "@/components/Providers";
// ...
// <body> <Providers>{children}</Providers> </body>

// --- 在客户端组件中使用 useSession ---
// components/UserProfile.tsx
// 'use client';
// import { useSession, signIn, signOut } from "next-auth/react";
//
// export default function UserProfile() {
// const { data: session, status } = useSession();
//
// if (status === "loading") return <p>加载中...</p>;
//
// if (session) {
// return (
// <>
// 欢迎回来, {session.user?.name}! <br />
// <button onClick={() => signOut()}>退出登录</button>
// </>
// );
// }
// return (
// <>
// 您尚未登录 <br />
// <button onClick={() => signIn()}>登录</button> {/* signIn() 会跳转到登录页 */}
// </>
// );
// }

JWT (JSON Web Tokens)

说明: 一种开放标准 (RFC 7519),用于在各方之间安全地传输信息(声明)作为 JSON 对象。常用于认证和信息交换。服务器在用户登录后生成一个 JWT,客户端存储它(通常在 LocalStorage 或 HttpOnly Cookie 中),并在后续请求中通过 Authorization 头发送给服务器。服务器验证 JWT 的签名以确认其有效性。

示例 (概念性 - 手动处理):

// pages/api/login.ts (模拟登录并返回 JWT)
import jwt from "jsonwebtoken"; // 需要安装 jsonwebtoken
import bcrypt from "bcryptjs"; // 需要安装 bcryptjs

const SECRET_KEY = process.env.JWT_SECRET || "your-very-secret-key";

export default async function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).end();
}

const { username, password } = req.body;

// 1. 查找用户 (模拟)
const user = {
id: "user123",
username: "testuser",
passwordHash: await bcrypt.hash("password123", 10),
}; // 实际应从数据库查找

// 2. 验证密码
const passwordIsValid = await bcrypt.compare(password, user.passwordHash);

if (user && passwordIsValid) {
// 3. 生成 JWT
const token = jwt.sign(
{ userId: user.id, username: user.username }, // Payload - 不要包含敏感信息
SECRET_KEY,
{ expiresIn: "1h" } // Token 过期时间
);
res.status(200).json({ token });
} else {
res.status(401).json({ message: "无效的用户名或密码" });
}
}

// pages/api/protected-resource.ts (验证 JWT)
import jwt from "jsonwebtoken";

const SECRET_KEY = process.env.JWT_SECRET || "your-very-secret-key";

export default function handler(req, res) {
try {
// 1. 从 Authorization header 获取 token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ message: "缺少认证 Token" });
}
const token = authHeader.split(" ")[1];

// 2. 验证 Token
const decoded = jwt.verify(token, SECRET_KEY);

// 3. Token 有效,处理请求 (decoded 包含 payload)
// console.log('认证用户:', decoded.userId);
res
.status(200)
.json({ message: `这是受保护的数据,用户 ID: ${decoded.userId}` });
} catch (error) {
// Token 无效或过期
console.error("JWT 验证失败:", error.message);
res.status(401).json({ message: "无效或过期的 Token" });
}
}

Session (服务器端会话)

说明: 一种传统的认证机制。用户登录后,服务器创建一个会话,并将会话 ID 通过 Cookie 发送给客户端。客户端在后续请求中携带此 Cookie。服务器根据会话 ID 查找存储在服务器端(内存、数据库、缓存)的会话数据,以识别用户并获取其状态。这是有状态的机制。NextAuth.js 可以通过数据库适配器实现此策略。

OAuth (开放授权)

说明: 允许用户使用第三方服务的账户(如 Google, Facebook, GitHub)登录你的应用,而无需直接处理用户的密码。你的应用从第三方获取授权令牌或用户信息。NextAuth.js 极大地简化了 OAuth 提供商的集成。

示例: (见上方 NextAuth.js 示例中配置 GithubProvider)

自定义认证 (Custom Authentication)

说明: 实现你自己的认证逻辑,例如基于用户名/密码、手机验证码或其他特定方式。NextAuth.js 提供了 CredentialsProvider 来集成自定义的用户名/密码验证逻辑。如果完全手动实现,需要处理密码哈希存储(如 bcrypt)、用户查找、会话或 Token 管理等所有细节。

示例: (见上方 NextAuth.js 示例中配置 CredentialsProvider)

2. 授权 (Authorization)

在用户身份被验证后,确定他们有权访问哪些资源或执行哪些操作的过程。

角色权限 (Role-Based Access Control - RBAC)

说明: 为用户分配角色(如 admin, editor, viewer),并根据角色授予不同的权限。在访问资源或执行操作前,检查用户的角色是否满足所需的权限级别。用户角色信息通常存储在会话或 JWT 的 payload 中。

示例 (在 API Route 中检查角色):

// pages/api/admin/data.ts
import { getToken } from "next-auth/jwt"; // 使用 next-auth 获取 JWT payload

const secret = process.env.NEXTAUTH_SECRET;

export default async function handler(req, res) {
// 获取与请求关联的 JWT token (如果使用 JWT 策略)
const token = await getToken({ req, secret });

// 检查用户是否存在且角色是否为 'admin'
// (假设 token payload 中有 role 字段)
if (token && token.role === "admin") {
// 用户是管理员,允许访问
res.status(200).json({ data: "这是管理员专属数据" });
} else {
// 用户未登录或不是管理员,拒绝访问
res.status(403).json({ message: "禁止访问" });
}
}

// 注意:需要配置 next-auth 的 jwt callback 来将 role 添加到 token payload
// callbacks: {
// async jwt({ token, user }) {
// if (user?.role) { // 假设 user 对象从 authorize 或数据库获取时包含 role
// token.role = user.role;
// }
// return token;
// },
// async session({ session, token }) { // 如果需要将 role 加入 session 对象
// session.user.role = token.role;
// return session;
// }
// }

路由保护 (Route Protection)

说明: 限制对特定页面或路由的访问,仅允许已认证或具有特定角色的用户访问。

  • Middleware: 是推荐的方式,可以在请求到达页面前进行检查和重定向。
  • App Router (Server Component/Layout): 在 Server Component 或 Layout 中获取会话/用户信息,根据认证状态或角色条件渲染内容或重定向。
  • Pages Router (Client-side): 在组件挂载时使用 useSession 检查状态,如果未授权则使用 useRouter 重定向。也可使用 HOC (Higher-Order Component)。

示例 (Middleware):

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";

const secret = process.env.NEXTAUTH_SECRET;

export async function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;

// 检查是否访问受保护的管理后台路径
if (pathname.startsWith("/admin")) {
const token = await getToken({ req, secret });

// 如果没有 token (未登录) 或 token 中角色不是 admin
if (!token || token.role !== "admin") {
// 重定向到登录页或禁止访问页
const url = req.nextUrl.clone();
url.pathname = "/api/auth/signin"; // 或 '/unauthorized'
// 可以附加 callbackUrl 参数让登录后返回原页面
url.searchParams.set("callbackUrl", req.nextUrl.href);
return NextResponse.redirect(url);
}
}

// 继续处理其他请求
return NextResponse.next();
}

export const config = {
matcher: "/admin/:path*", // 只对 /admin/* 路径应用此中间件
};

API 权限 (API Permissions)

说明: 保护你的 API 端点 (Route Handlers 或 API Routes),确保只有经过授权的用户才能调用它们。在 API 处理函数内部检查用户的认证状态和权限(通常通过解析 Session 或 JWT 实现)。

示例: (见上方 "角色权限" 中的 API Route 示例)

中间件认证 (Middleware Authentication)

说明: 使用 Next.js Middleware 进行集中的认证和授权检查。这是实现路由保护和 API 权限检查的有效方式,因为它在请求处理的早期阶段执行。

示例: (见上方 "路由保护" 中的 Middleware 示例)

CORS 配置 (Cross-Origin Resource Sharing)

说明: CORS 是一种浏览器安全机制,用于控制来自不同源(域、协议、端口)的 Web 页面对当前源的资源发起的请求。如果你的 Next.js API 需要被不同域的前端应用调用,你需要在 API 响应中添加 CORS 头,以允许这些跨域请求。

示例 (next.config.js - 针对 Pages Router API Routes):

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
// 应用于 /api/* 下的所有路由
source: "/api/:path*",
headers: [
// 允许来自 example.com 的请求
{
key: "Access-Control-Allow-Origin",
value: "https://www.example.com",
},
// 允许的 HTTP 方法
{
key: "Access-Control-Allow-Methods",
value: "GET, POST, PUT, DELETE, OPTIONS",
},
// 允许的请求头
{
key: "Access-Control-Allow-Headers",
value: "X-Requested-With, Content-Type, Authorization",
},
// 允许携带凭证 (如 cookies)
{ key: "Access-Control-Allow-Credentials", value: "true" },
],
},
];
},
// ... 其他配置
};
module.exports = nextConfig;

示例 (App Router - Route Handler):

// app/api/some-data/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
// ... 获取数据的逻辑 ...
const data = { message: "来自允许跨域的 API 数据" };

// 创建 NextResponse 并手动设置 CORS 头
const response = NextResponse.json(data);
response.headers.set(
"Access-Control-Allow-Origin",
"https://www.example.com"
); // 允许特定源
// response.headers.set('Access-Control-Allow-Origin', '*'); // 允许任何源 (谨慎使用)
response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
response.headers.set("Access-Control-Allow-Credentials", "true");

return response;
}

// 对于 OPTIONS 请求 (预检请求),通常需要单独处理并返回允许的头信息
export async function OPTIONS(request: Request) {
const response = new NextResponse(null, { status: 204 }); // 204 No Content
response.headers.set(
"Access-Control-Allow-Origin",
"https://www.example.com"
);
response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
);
response.headers.set("Access-Control-Allow-Credentials", "true");
response.headers.set("Access-Control-Max-Age", "86400"); // 预检结果缓存时间 (秒)
return response;
}

七、部署与运维

将 Next.js 应用部署到生产环境并进行维护。

1. 部署选项

Vercel

说明: 由 Next.js 的创建者提供的托管平台。与 Next.js 无缝集成,提供零配置部署、自动扩展、全球 Edge Network (CDN)、Serverless Functions、Edge Functions (用于 Middleware)、内置 CI/CD(基于 Git)、预览部署、环境变量管理等功能。是部署 Next.js 应用最简单、最推荐的方式之一。

示例 (通过 Vercel CLI 部署):

# 1. 安装 Vercel CLI (如果尚未安装)
npm install -g vercel
# 或者 yarn global add vercel
# 或者 pnpm add -g vercel

# 2. 登录 Vercel 账户
vercel login

# 3. 在项目根目录链接项目 (首次)
vercel link

# 4. 部署到预览环境 (会生成一个唯一 URL)
vercel

# 5. 将当前预览部署提升到生产环境
vercel --prod

部署通常通过连接 Git 仓库 (GitHub, GitLab, Bitbucket) 自动进行,每次 push 时触发构建和部署。

Docker

说明: 使用 Docker 将 Next.js 应用打包成一个容器镜像,确保在任何支持 Docker 的环境中都能以一致的方式运行。需要创建一个 Dockerfile 来定义构建步骤和运行环境。推荐使用 Next.js 的 standalone 输出模式 (output: 'standalone'next.config.js) 来减小 Docker 镜像体积。

示例 (Dockerfile 使用 standalone 输出):

# Dockerfile for Next.js with standalone output

#--- 构建阶段 ----
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app

# 安装构建依赖 (如果需要,例如 Prisma)
# RUN apk add --no-cache openssl

# 拷贝 package.json 和 lock 文件
COPY package*.json ./

# 安装依赖
# 如果使用 pnpm
# RUN corepack enable && pnpm install --frozen-lockfile
# 如果使用 yarn
# RUN yarn install --frozen-lockfile
# 如果使用 npm
RUN npm ci

# 拷贝项目剩余文件
COPY . .

# 设置构建时环境变量 (如果需要)
# ARG NEXT_PUBLIC_API_URL
# ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

# 执行构建 (确保 next.config.js 中已设置 output: 'standalone')
RUN npm run build
# 或者 yarn build / pnpm build

#--- 运行阶段 ----
FROM node:18-alpine AS runner
WORKDIR /app

# 设置生产环境
ENV NODE_ENV=production
# 禁用遥测数据 (可选)
ENV NEXT_TELEMETRY_DISABLED 1

# 将构建阶段生成的 standalone 文件拷贝过来
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

# 暴露 Next.js 默认运行的端口 (或你在 package.json 中指定的端口)
EXPOSE 3000

# 设置启动命令,运行 standalone 模式下的 server.js
CMD ["node", "server.js"]

# 最佳实践:以非 root 用户运行 (node 用户在 node:alpine 镜像中通常存在)
# USER node
// next.config.js (确保开启 standalone 模式)
/** @type {import('next').NextConfig} */
const nextConfig = {
// ... 其他配置
output: "standalone", // 启用 standalone 输出模式
};
module.exports = nextConfig;

构建和运行:

# 构建 Docker 镜像
docker build -t my-nextjs-app .

# 运行 Docker 容器
docker run -p 3000:3000 --name my-nextjs-container my-nextjs-app

自托管 (Self-hosting)

说明: 将 Next.js 应用部署到自己的服务器(如 VPS、物理服务器)。需要手动配置 Node.js 环境、安装依赖、构建应用 (npm run build)、运行应用 (npm start)。通常需要使用进程管理器(如 PM2、systemd)来确保应用在后台稳定运行,并配置反向代理(如 Nginx、Apache)来处理 HTTPS、负载均衡和静态资源服务。同样推荐使用 standalone 输出模式。

示例 (使用 PM2 管理进程):

# 1. 全局安装 PM2
npm install -g pm2

# 2. 在服务器上构建应用 (或从 CI/CD 同步构建产物)
cd /path/to/your/app
npm run build # 确保 output: 'standalone' 已配置

# 3. 创建 PM2 配置文件 ecosystem.config.js
// ecosystem.config.js (PM2 配置文件示例)
module.exports = {
apps: [
{
name: "my-nextjs-app", // 应用名称
script: "server.js", // 指向 .next/standalone/server.js
cwd: ".next/standalone", // 设置工作目录为 standalone 目录
instances: "max", // 根据 CPU 核心数启动实例,或者指定数字
exec_mode: "cluster", // 启用集群模式,提高性能和稳定性
env: {
// 定义环境变量
NODE_ENV: "production",
PORT: 3000, // Next.js 监听的端口
// DATABASE_URL: '...',
// NEXTAUTH_SECRET: '...'
},
},
],
};
# 4. 使用 PM2 启动应用
pm2 start ecosystem.config.js

# 其他常用 PM2 命令
# pm2 list # 查看应用状态
# pm2 logs # 查看日志
# pm2 restart all # 重启所有应用
# pm2 stop all # 停止所有应用
# pm2 delete all # 删除所有应用
# pm2 startup # 生成开机自启脚本
# pm2 save # 保存当前应用列表,以便重启后恢复

示例 (Nginx 作为反向代理):

# /etc/nginx/sites-available/my-nextjs-app.conf
server {
listen 80;
server_name yourdomain.com www.yourdomain.com; # 你的域名

# 推荐:配置 HTTPS (使用 Let's Encrypt 或其他证书)
# listen 443 ssl http2;
# server_name yourdomain.com www.yourdomain.com;
# ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

# 将所有请求代理到 Next.js 运行的端口 (例如 PM2 中配置的 3000)
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# 可选:让 Nginx 直接处理 Next.js 的静态文件,减轻 Node 服务的压力
# location /_next/static {
# alias /path/to/your/app/.next/static; # 指向你的静态文件目录
# expires 1y; # 设置强缓存
# access_log off;
# }
}

CDN 配置 (Content Delivery Network)

说明: CDN 将应用的静态资源(JS、CSS、图片、字体等)缓存到全球各地的边缘节点。用户访问时从最近的节点获取资源,减少延迟,提高加载速度,并降低源服务器的负载。

  • Vercel: 自动通过其 Edge Network 提供 CDN 功能。
  • 自托管/Docker: 需要手动配置 CDN 服务(如 Cloudflare, AWS CloudFront, Akamai),使其指向你的服务器源站或将构建后的静态资源上传到 CDN 存储桶。
  • assetPrefix: 如果静态资源部署到与 Next.js 应用不同的域名(CDN 域名),需要在 next.config.js 中配置 assetPrefix

示例 (next.config.js 配置 assetPrefix):

// next.config.js
const isProd = process.env.NODE_ENV === "production";
const CDN_URL = "https://cdn.yourdomain.com"; // 你的 CDN 域名

/** @type {import('next').NextConfig} */
const nextConfig = {
// 仅在生产环境配置 assetPrefix
assetPrefix: isProd ? CDN_URL : undefined,
// ... 其他配置
};
module.exports = nextConfig;

环境变量 (Environment Variables)

说明: 用于配置应用行为,区分不同环境(开发、预览、生产),存储敏感信息(API 密钥、数据库连接字符串)。

  • 文件: .env (默认), .env.local (覆盖所有,不提交 git), .env.development, .env.production
  • 访问: 服务端代码 (process.env.VAR_NAME),客户端代码 (process.env.NEXT_PUBLIC_VAR_NAME)。
  • 部署平台: Vercel、Netlify 等平台提供了 UI 或 CLI 来设置生产环境变量。
  • Docker: 通过 -e 参数、--env-file 或 Docker Compose 文件设置。
  • 自托管: 通过系统环境变量、.env 文件(需加载库如 dotenv,但 Next.js 内置支持)或进程管理器(如 PM2 的 env 配置)设置。

示例: (已在之前章节详细说明,此处不再重复)

2. 运维与监控

错误追踪 (Error Tracking)

说明: 集成第三方错误追踪服务(如 Sentry, Bugsnag, Rollbar)来捕获、聚合和分析应用在生产环境中发生的错误(客户端和服务端)。这比仅依赖服务器日志更高效,提供错误上下文、发生频率、影响用户数等信息,并支持告警。

示例 (集成 Sentry - 概念性): (已在上一章节详细说明,此处不再重复)

日志管理 (Logging Management)

说明: 收集、存储、搜索和分析应用日志。对于 Server Components、API Routes、Middleware 和 getServerSideProps,日志通常输出到标准输出/错误流。部署平台(如 Vercel)通常会收集这些日志。对于自托管,需要配置日志收集工具(如 Fluentd, Logstash)将日志转发到集中式日志平台(如 Elasticsearch, Graylog, Datadog Logs)。使用结构化日志(JSON 格式)可以方便后续的查询和分析。

示例 (使用 Pino 进行结构化日志 - 概念性): (已在上一章节详细说明,此处不再重复)

性能监控 (Performance Monitoring)

说明: 持续监控应用的关键性能指标,如服务器响应时间、数据库查询性能、API 延迟、资源使用率 (CPU, Memory)、Core Web Vitals (LCP, FID/INP, CLS) 等。使用 APM (Application Performance Monitoring) 工具(如 Datadog, New Relic, Dynatrace)或平台内置监控(如 Vercel Analytics)。目的是及早发现性能瓶颈和问题。

示例 (关联 reportWebVitals): Next.js 的 reportWebVitals 功能是将前端性能指标 (CWV) 发送到监控系统的入口点。 (已在性能优化章节详细说明,此处不再重复)

用户分析 (User Analytics)

说明: 跟踪用户行为,了解用户如何与应用交互,例如页面浏览量、用户来源、事件触发、转化漏斗等。集成第三方分析工具(如 Google Analytics, Plausible Analytics, Mixpanel, Amplitude)。通常在客户端通过嵌入跟踪代码或使用 SDK 来实现。

示例 (Google Analytics 集成 - 概念性): (已在上一章节详细说明,此处不再重复)

SEO 优化 (Search Engine Optimization)

说明: 运维阶段持续关注 SEO 表现。

  • 监控排名: 使用工具(如 Google Search Console, SEMrush, Ahrefs)监控关键词排名和索引状态。
  • 技术 SEO 检查: 定期检查网站的可爬行性、索引错误、robots.txtsitemap.xml、结构化数据、页面速度 (Core Web Vitals)。
  • 内容更新: 保持内容新鲜和相关性。
  • 链接建设: 获取高质量的外链。
  • 日志分析: 分析爬虫日志,了解搜索引擎如何抓取网站。

示例 (生成 sitemap.xml - 基础 App Router 示例):

// app/sitemap.ts (或 .js)
import { MetadataRoute } from "next";

// 假设有一个函数获取所有文章的 slugs
async function getAllPostSlugs(): Promise<{ slug: string; updatedAt: Date }[]> {
// ... 从数据库或 CMS 获取文章列表 ...
return [
{ slug: "post-1", updatedAt: new Date() },
{ slug: "post-2", updatedAt: new Date("2023-10-26") },
];
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = "https://yourdomain.com"; // 你的网站基础 URL

// 获取动态路由数据
const posts = await getAllPostSlugs();
const postEntries: MetadataRoute.Sitemap = posts.map(
({ slug, updatedAt }) => ({
url: `${baseUrl}/posts/${slug}`,
lastModified: updatedAt,
changeFrequency: "weekly", // 更新频率 (可选)
priority: 0.8, // 优先级 (可选)
})
);

return [
{
url: baseUrl, // 首页
lastModified: new Date(),
changeFrequency: "daily",
priority: 1,
},
{
url: `${baseUrl}/about`, // 关于页面 (静态)
lastModified: new Date(), // 或者指定一个固定的修改日期
changeFrequency: "monthly",
priority: 0.5,
},
...postEntries, // 添加动态生成的文章条目
];
}

Next.js 会在构建时或请求时(取决于配置)自动在 /sitemap.xml 路径生成站点地图。类似地,可以创建 app/robots.ts 来生成 robots.txt

八、工具集成

Next.js 与众多开发和构建工具链生态系统良好集成。

1. 开发工具

TypeScript

说明: 为 JavaScript 添加静态类型检查。Next.js 对 TypeScript 提供了一流的支持,可以在创建项目时选择 TypeScript 模板 (create-next-app --ts)。有助于在开发阶段捕获错误,提高代码可维护性和开发效率。

示例: (已在之前章节详细说明,此处不再重复)

ESLint

说明: JavaScript 和 TypeScript 代码检查工具,用于发现代码中的潜在问题和强制执行代码风格。Next.js 内置了 ESLint 配置 (next lint),开箱即用,推荐与 Prettier 配合使用。

示例 (.eslintrc.json): (已在之前章节详细说明,此处不再重复)

Prettier

说明: 代码格式化工具,自动统一代码风格(如缩进、引号、分号等)。通常配置为在保存文件时或提交代码前自动运行,确保代码库风格一致。

示例 (.prettierrc.json): (已在之前章节详细说明,此处不再重复)

Jest

说明: 流行的 JavaScript 测试框架,常用于单元测试和集成测试。在 Next.js 中使用 Jest 需要一些配置(例如通过 Babel 处理 JSX/TS,模拟 Next.js 特有模块如 next/router,配置 CSS Modules 等)。通常与 React Testing Library 结合使用来测试 React 组件。

示例 (配合 React Testing Library 测试组件):

// components/MyComponent.tsx
import React from "react";
export default function MyComponent({ text }: { text: string }) {
return <div>{text}</div>;
}

// components/MyComponent.test.tsx
import React from "react";
import { render, screen } from "@testing-library/react"; // 需要安装 @testing-library/react
import "@testing-library/jest-dom"; // 需要安装 @testing-library/jest-dom (提供更多断言)
import MyComponent from "./MyComponent";

// Jest 配置 (通常在 jest.config.js 或 package.json 中)
// 需要设置 testEnvironment: 'jsdom'
// 需要配置 transform 以处理 TS/JSX (例如使用 babel-jest 或 @swc/jest)
// 需要配置 moduleNameMapper 来处理 CSS Modules, 图片导入等

describe("MyComponent", () => {
it("renders the text passed as prop", () => {
const testMessage = "Hello Jest!";
render(<MyComponent text={testMessage} />);

// 使用 React Testing Library 的查询方法
const element = screen.getByText(testMessage);

// 使用 @testing-library/jest-dom 的断言
expect(element).toBeInTheDocument();
});
});

Cypress

说明: 端到端 (E2E) 测试框架,用于在真实浏览器环境中模拟用户交互,测试整个应用流程。可以测试页面加载、表单提交、导航、API 交互等。

示例: (已在之前章节详细说明,此处不再重复)

Storybook

说明: 用于独立开发、测试和展示 UI 组件的工具。创建一个隔离的环境,方便查看组件在不同状态和 props 下的表现,促进组件复用和团队协作。

示例: (已在之前章节详细说明,此处不再重复)

2. 构建工具

Next.js 内部封装了复杂的构建流程,但有时需要进行定制。

Webpack 配置

说明: Next.js 主要使用 Webpack 作为其底层的模块打包器 (开发环境正逐步迁移到 Turbopack)。可以通过 next.config.js 中的 webpack 函数来修改 Webpack 配置,例如添加自定义的 Loader、Plugin、Alias 等。修改时需谨慎,以免破坏 Next.js 的内部构建逻辑。

示例 (添加 Webpack Alias): (已在之前章节详细说明,此处不再重复)

Babel 设置

说明: Next.js 使用 Babel 来转换 JavaScript/TypeScript 代码。它自带了一个优化的 Babel preset (next/babel)。如果需要使用特定的 Babel 插件或修改配置,可以创建 .babelrc 文件,但通常不建议深度修改默认配置。

示例 (.babelrc): (已在之前章节详细说明,此处不再重复)

PostCSS

说明: Next.js 使用 PostCSS 来处理 CSS,默认集成了 Autoprefixer。如果使用 Tailwind CSS 或其他 PostCSS 插件,需要在项目根目录创建 postcss.config.js 文件进行配置。

示例 (postcss.config.js): (已在之前章节详细说明,此处不再重复)

环境变量 (Build Time)

说明: 在构建过程中使用的环境变量。NEXT_PUBLIC_ 前缀的变量会被内联到客户端 JavaScript 包中。服务端环境变量可用于构建时的逻辑判断(如在 getStaticProps 中根据环境调用不同 API)或配置(如 assetPrefix)。确保在构建环境(CI/CD、部署平台)中正确设置了这些变量。

示例: (已在之前章节详细说明,此处不再重复)

自定义插件 (Custom Plugins/Loaders)

说明: 对于高级用例,可以通过 next.config.jswebpack 函数添加自定义的 Webpack Loader 来处理特定文件类型,或添加自定义的 Webpack Plugin 来在构建的不同阶段执行特定任务(如生成文件、修改资源等)。

示例: (已在之前章节详细说明,此处不再重复)

九、最佳实践

遵循最佳实践有助于构建可维护、高性能、可扩展的 Next.js 应用。

1. 项目架构

目录结构 (Directory Structure)

说明: 采用清晰、一致的目录结构。除了 Next.js 约定的 apppages, public, next.config.js 外,推荐根据功能或类型组织代码。

  • components/: 存放可复用的 UI 组件(细分为 ui/, common/, features/ 等)。
  • lib/utils/: 存放工具函数、辅助方法、常量等。
  • hooks/: 存放自定义 React Hooks。
  • contexts/store/: 存放状态管理相关代码(如 React Context, Zustand store)。
  • styles/: 存放全局样式、主题配置、基础样式等。
  • types/interfaces/: 存放共享的 TypeScript 类型定义。
  • services/api-clients/: 存放 API 请求相关的逻辑封装。

示例 (推荐结构):

my-next-app/
├── app/ # App Router 核心目录
│ ├── (marketing)/ # 路由分组
│ │ └── ...
│ ├── api/ # API 路由
│ │ └── ...
│ ├── dashboard/ # 功能模块路由
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── layout.tsx # 根布局
│ └── page.tsx # 根页面
├── components/ # 可复用组件
│ ├── ui/ # 基础 UI 元素 (Button, Input)
│ ├── common/ # 通用组合组件 (Header, Footer, Card)
│ └── features/ # 特定功能相关的组件 (UserProfile, ProductList)
├── lib/ # 工具函数/常量/配置
│ ├── utils.ts
│ ├── constants.ts
│ └── fetchApi.ts # API 请求封装
├── hooks/ # 自定义 Hooks
│ └── useAuth.ts
├── contexts/ # React Context
│ └── ThemeContext.tsx
├── public/ # 静态资源
├── styles/ # 全局样式/主题
│ ├── globals.css
│ └── theme.ts
├── types/ # TypeScript 类型定义
│ └── index.ts
├── .env.local # 本地环境变量
├── next.config.js # Next.js 配置文件
├── tsconfig.json # TypeScript 配置文件
└── package.json

代码组织 (Code Organization)

说明: 遵循模块化和关注点分离原则。将相关逻辑(UI、状态、数据获取、工具函数)组织在一起(例如按功能组织),但保持文件和组件的单一职责。避免创建过于庞大的组件或文件。

示例 (功能目录结构):

my-next-app/
├── features/ # 按功能组织代码
│ ├── auth/ # 认证功能
│ │ ├── components/ # 认证相关组件 (LoginForm)
│ │ ├── hooks/ # 认证相关 Hooks (useAuth)
│ │ ├── services/ # 认证 API 调用
│ │ └── types.ts # 认证相关类型
│ ├── products/ # 产品功能
│ │ ├── components/ # 产品相关组件 (ProductCard, ProductDetail)
│ │ ├── services/
│ │ └── page.tsx # 产品列表页面 (若使用 App Router)
│ └── ...
├── components/ # 通用、与特定功能无关的组件
├── lib/ # 通用工具库
└── ...

组件设计 (Component Design)

说明:

  • 可复用性: 设计通用、可配置的组件。
  • 原子性: 尽量保持组件小而专注(单一职责)。
  • 组合: 通过组合小组件来构建复杂 UI,而不是继承。
  • Props 设计: Props 接口清晰、简洁,避免传递过多不相关的 props。
  • Server vs Client: 明智地选择组件类型。将交互、状态、浏览器 API 限制在客户端组件 ('use client') 中,尽可能使用服务端组件获取数据和渲染静态内容。

示例 (组合与 Server/Client):

// components/ui/Button.tsx ('use client' 因为有 onClick)
"use client";
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
export default function Button({ children, ...props }: ButtonProps) {
return <button {...props}>{children}</button>;
}

// components/common/Card.tsx (可以是 Server Component,如果内容静态)
type CardProps = { title: string; children: React.ReactNode };
export default function Card({ title, children }: CardProps) {
return (
<div style={{ border: "1px solid #ccc", padding: "1rem", margin: "1rem" }}>
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}

// app/some-feature/page.tsx (Server Component 组合 Card 和 Button)
import Card from "@/components/common/Card";
import Button from "@/components/ui/Button";

async function getData() {
/* ... 获取数据 ... */ return "一些数据";
}

export default async function FeaturePage() {
const data = await getData();
const handleButtonClick = () => {
// 这个事件处理函数需要移到 Client Component
"use server"; // 或者使用 Server Action (如果适用)
console.log("按钮被点击!");
};

return (
<Card title="功能页面">
<p>从服务端获取的数据: {data}</p>
{/* Button 是客户端组件,可以在 Server Component 中渲染 */}
{/* 但如果需要交互,最好将交互逻辑封装在 Client Component 中 */}
<Button onClick={() => alert("客户端交互!")}>点我 (客户端)</Button>

{/* 对于需要 Server 端逻辑的 Button,考虑 Server Actions */}
{/*
<form action={async () => {
'use server';
console.log('Server Action 执行!');
}}>
<Button type="submit">执行 Server Action</Button>
</form>
*/}
</Card>
);
}

状态管理 (State Management)

说明: 根据应用复杂度和状态共享范围选择合适的方案。

  • 简单局部状态: useState, useReducer
  • 跨层级共享 (简单): React Context (useContext)。
  • 服务端状态/缓存: React Query / TanStack Query, SWR。
  • 复杂全局/跨功能状态: Zustand, Jotai, Redux Toolkit。
  • URL 状态: 使用 useRouter, useSearchParams 管理可以通过 URL 表示的状态(筛选、分页)。
  • Server Components: 优先在服务端获取数据,减少客户端状态管理的需求。

示例: (已在之前数据处理章节提供各种方案示例)

API 设计 (API Design)

说明:

  • API Routes / Route Handlers: 设计清晰、符合 RESTful 规范或使用 GraphQL/tRPC。
  • 请求/响应: 定义明确的请求参数和响应结构(使用 TypeScript 类型)。
  • 状态码: 正确使用 HTTP 状态码 (200, 201, 400, 401, 403, 404, 500)。
  • 错误处理: 提供有意义的错误信息。
  • 验证: 对输入进行严格验证(推荐使用 zod)。
  • 安全性: 保护 API 端点(认证、授权、限流)。

示例 (Route Handler with Zod):

// app/api/items/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";

// 定义请求体 Schema
const itemSchema = z.object({
name: z.string().min(3, { message: "名称至少需要3个字符" }),
price: z.number().positive({ message: "价格必须是正数" }),
});

export async function POST(request: NextRequest) {
let requestData;
try {
requestData = await request.json();
} catch (error) {
return NextResponse.json({ error: "无效的 JSON 数据" }, { status: 400 });
}

// 使用 Zod 验证数据
const validationResult = itemSchema.safeParse(requestData);

if (!validationResult.success) {
// 验证失败,返回详细错误
return NextResponse.json(
{ errors: validationResult.error.flatten().fieldErrors },
{ status: 400 }
);
}

// 验证成功,处理数据 (validationResult.data 是类型安全的数据)
const newItem = validationResult.data;
console.log("创建新物品:", newItem);
// ... 保存到数据库等操作 ...

return NextResponse.json(
{ message: "物品创建成功", item: newItem },
{ status: 201 }
);
}

2. 性能优化

首屏加载 (Initial Load Performance)

说明: 优化 LCP (Largest Contentful Paint) 和 FCP (First Contentful Paint)。

  • 渲染策略: 优先使用 SSG/ISR 处理静态或不常变化的内容。SSR 用于动态内容。
  • Server Components: 充分利用其减少客户端 JS 的优势。
  • 图片优化: 使用 next/image 并为 LCP 图片添加 priority 属性。
  • 字体优化: 使用 next/font 优化字体加载,避免布局偏移和 FOIT/FOUT。
  • 关键 CSS: Next.js 会自动内联关键 CSS。
  • 减少阻塞渲染资源: 避免在 <head> 中加载大型或非必需的同步脚本。

示例 (next/font):

// app/layout.tsx
import { Inter } from "next/font/google"; // 导入字体函数

// 配置字体
const inter = Inter({
subsets: ["latin"], // 指定需要的子集
display: "swap", // 字体加载策略
// weight: ['400', '700'] // 可选:指定需要的字重
});

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
// 将字体类名应用到 HTML 或 Body 元素
<html lang="zh-CN" className={inter.className}>
<body>{children}</body>
</html>
);
}

代码分割 (Code Splitting)

说明: 确保只加载当前页面或交互所需的代码。

  • 自动分割: Next.js 基于页面进行自动代码分割。
  • 动态导入: 使用 next/dynamic 按需加载大型组件、第三方库或非首屏内容。

示例 (next/dynamic): (已在基础概念章节提供)

缓存策略 (Caching Strategy)

说明: 有效利用缓存减少服务器负载和提高响应速度。

  • 数据缓存: 在 App Router 中合理使用 fetchcacherevalidate 选项。使用 tags 进行按需重新验证。
  • CDN 缓存: 配置 CDN 缓存静态资源(JS, CSS, Images)。
  • 浏览器缓存: 通过 HTTP 缓存头(如 Cache-Control)控制浏览器如何缓存资源。Next.js 对 _next/static 下的资源默认设置了长期缓存。

示例 (fetch 缓存): (已在数据处理章节提供)

SEO 优化 (Search Engine Optimization)

说明: 确保搜索引擎可以有效地发现、抓取和理解你的内容。

  • 元数据: 使用 Metadata API (App Router) 或 next/head (Pages Router) 设置 title, description, Open Graph 标签等。
  • 语义化 HTML: 使用正确的 HTML 标签 (<main>, <nav>, <article>, <h1>-<h6>)。
  • 结构化数据: 使用 JSON-LD 添加结构化数据,帮助搜索引擎理解内容。
  • sitemap.xml & robots.txt: 提供站点地图和爬虫规则。
  • 内部链接: 合理组织内部链接结构。
  • 性能: 页面加载速度是重要的排名因素 (Core Web Vitals)。

示例 (Metadata API):

// app/products/[id]/page.tsx
import { Metadata, ResolvingMetadata } from "next";

type Props = { params: { id: string } };

// 假设有函数获取产品数据
async function getProduct(
id: string
): Promise<{ name: string; description: string } | null> {
// ... fetch product data ...
return { name: `产品 ${id}`, description: `产品 ${id} 的详细描述` };
}

// 动态生成元数据
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata // 可以访问父级(layout)解析出的 metadata
): Promise<Metadata> {
const id = params.id;
const product = await getProduct(id);

if (!product) {
return { title: "产品未找到" }; // 返回默认或错误标题
}

// const previousImages = (await parent).openGraph?.images || []; // 访问父级 Open Graph 图片

return {
title: `${product.name} - 我的网站`,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
// images: ['/some-specific-page-image.jpg', ...previousImages], // 添加图片
},
};
}

export default async function ProductPage({ params }: Props) {
const product = await getProduct(params.id);
if (!product) return <div>产品未找到</div>;
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}

可访问性 (Accessibility - a11y)

说明: 确保应用对所有用户(包括残障人士)可用。

  • 语义化 HTML: 使用正确的标签。
  • 键盘导航: 确保所有交互元素可通过键盘访问和操作。
  • 焦点管理: 合理管理焦点状态。
  • ARIA 属性: 在需要时使用 ARIA (Accessible Rich Internet Applications) 属性增强语义。
  • 颜色对比度: 确保文本和背景有足够的对比度。
  • 图片 Alt 文本: 为所有有意义的图片提供描述性的 alt 属性。
  • 表单标签: 使用 <label> 关联表单控件。
  • 测试: 使用自动化工具 (axe-core) 和手动测试 (键盘导航、屏幕阅读器)。

示例 (基础可访问性实践):

function AccessibleForm() {
return (
<form>
{/* 使用 label 关联 input */}
<label htmlFor="username">用户名:</label>
<input type="text" id="username" name="username" aria-required="true" />

{/* 为图标按钮提供文本替代 */}
<button aria-label="设置">
<svg>{/* 设置图标 */}</svg>
</button>

{/* 确保自定义控件可通过键盘操作并具有适当的 ARIA 角色 */}
<div role="checkbox" aria-checked="false" tabIndex={0}>
自定义复选框
</div>
</form>
);
}

3. 开发流程

Git 工作流 (Git Workflow)

说明: 采用标准化的 Git 分支模型,如 Gitflow 或 GitHub Flow。

  • 主分支 (main/master): 代表稳定、可随时部署的生产代码。
  • 开发分支 (develop): 集成功能的分支(Gitflow)。
  • 功能分支 (feature/...): 开发新功能或修复 Bug 的独立分支。
  • Pull Requests (PRs): 将功能分支合并回主分支或开发分支前进行代码审查。

CI/CD (Continuous Integration / Continuous Deployment)

说明: 自动化构建、测试和部署流程。

  • CI (持续集成): 每次代码提交(到功能分支或合并到主分支)时,自动运行构建、代码检查和测试。
  • CD (持续部署/交付): CI 成功后,自动将应用部署到预览环境或生产环境。
  • 工具: Vercel (内置), GitHub Actions, GitLab CI, Jenkins。

示例 (GitHub Actions - 概念性):

# .github/workflows/ci-cd.yml
name: Next.js CI/CD

on:
push:
branches: [main] # 仅在 push 到 main 分支时触发
pull_request:
branches: [main] # 在向 main 分支提交 PR 时触发

jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # 检出代码
- name: Use Node.js
uses: actions/setup-node@v3 # 设置 Node.js 环境
with:
node-version: "18" # 指定 Node 版本
cache: "npm" # 缓存 npm 依赖

- name: Install Dependencies # 安装依赖
run: npm ci

- name: Lint Check # 代码检查
run: npm run lint

- name: Run Tests # 运行测试
run: npm test

- name: Build Project # 构建项目
run: npm run build
env: # 设置构建时环境变量 (如果需要)
NODE_ENV: production
# NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }} # 从 GitHub Secrets 获取

# deploy_to_vercel: # 可选:部署到 Vercel (通常 Vercel 会自动处理 Git 连接)
# needs: build_and_test
# runs-on: ubuntu-latest
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' # 仅在 push 到 main 时部署
# steps:
# - uses: actions/checkout@v3
# - name: Deploy to Vercel
# uses: amondnet/vercel-action@v20 # 或者使用 Vercel 官方 CLI
# with:
# vercel-token: ${{ secrets.VERCEL_TOKEN }}
# vercel-org-id: ${{ secrets.VERCEL_ORG_ID}}
# vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}}
# vercel-args: '--prod' # 部署到生产环境

测试策略 (Testing Strategy)

说明: 实施多层次的测试策略。

  • 单元测试 (Unit Testing): 测试独立的函数、组件或 Hook (Jest, React Testing Library)。
  • 集成测试 (Integration Testing): 测试多个单元如何协同工作(例如组件交互、页面级测试)。
  • 端到端测试 (E2E Testing): 在真实浏览器中模拟用户流程,测试整个应用 (Cypress, Playwright)。
  • 静态类型检查: 使用 TypeScript。
  • 代码覆盖率: 监控测试覆盖程度。

代码审查 (Code Review)

说明: 通过 Pull Request (PR) 进行代码审查。

  • 目标: 提高代码质量、发现潜在 Bug、确保一致性、知识共享。
  • 关注点: 代码逻辑、可读性、性能、安全性、测试覆盖率、是否符合规范。
  • 工具: GitHub, GitLab, Bitbucket 等平台内置的 PR 审查功能。

文档管理 (Documentation Management)

说明: 编写和维护必要的文档。

  • README.md: 项目概述、安装、运行、部署说明。
  • 组件文档: 使用 JSDoc、TypeScript 注释或 Storybook 为复杂或共享组件编写用法说明。
  • 架构决策记录 (ADRs): 记录重要的技术选型和设计决策。
  • API 文档: 如果提供 API,使用 Swagger/OpenAPI 或其他工具生成文档。

示例 (JSDoc):

/**
* 获取用户信息。
* @param userId - 要获取的用户的 ID。
* @param options - 可选的获取选项。
* @param options.includeDetails - 是否包含详细信息 (默认为 false)。
* @returns 返回用户对象 Promise,如果未找到则返回 null。
* @throws 如果 API 请求失败则抛出错误。
*/
async function fetchUser(
userId: string,
options?: { includeDetails?: boolean }
): Promise<User | null> {
// ... 实现逻辑 ...
}

十、高级特性

1. 国际化 (Internationalization - i18n)

next-i18next / next-intl

说明: next-i18next (基于 react-i18next) 和 next-intl 是两个流行的库,用于在 Next.js 应用中添加多语言支持。它们提供了加载翻译文件、管理当前语言环境、在组件中使用翻译文本的 Hooks 和 HOC。next-intl 对 App Router 的集成支持较好。

示例 (next-intl - App Router 概念性):

// middleware.ts (处理 locale 检测和重定向)
import createMiddleware from "next-intl/middleware";

export default createMiddleware({
locales: ["en", "zh"], // 支持的语言列表
defaultLocale: "zh", // 默认语言
});

export const config = {
// 跳过不需要国际化的路径
matcher: ["/((?!api|_next|.*\\..*).*)"],
};

// messages/en.json
// { "Greeting": "Hello, World!" }
// messages/zh.json
// { "Greeting": "你好,世界!" }

// app/[locale]/layout.tsx
import { NextIntlClientProvider, useMessages } from "next-intl";

export default function LocaleLayout({ children, params: { locale } }) {
const messages = useMessages(); // 获取当前 locale 的消息

return (
<html lang={locale}>
<body>
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}

// app/[locale]/page.tsx
import { useTranslations } from "next-intl";

export default function Home() {
const t = useTranslations(); // 获取翻译函数
return <h1>{t("Greeting")}</h1>; // 使用翻译函数
}

动态路由 (Dynamic Routing for i18n)

说明: Next.js 内置了 i18n 路由功能(通过 next.config.js 配置)。支持两种策略:

  • 子路径路由 (Sub-path Routing): 将 locale 添加到 URL 路径中 (例如 /en/about, /fr/about)。默认 locale 可以省略路径。
  • 域名路由 (Domain Routing): 为不同的 locale 分配不同的域名 (例如 example.com 对应英语, example.fr 对应法语)。

示例 (next.config.js - 子路径):

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
i18n: {
locales: ["en", "fr", "zh"], // 支持的语言环境列表
defaultLocale: "en", // 默认语言环境 (访问 `/` 时使用)
// localeDetection: false, // 可选:禁用基于 Accept-Language header 的自动检测
},
// ... 其他配置
};
module.exports = nextConfig;

注意: 内置 i18n 路由主要与 Pages Router 配合使用。App Router 推荐使用 next-intl 等库实现基于路径的国际化。

语言切换 (Language Switching)

说明: 提供 UI 让用户切换语言。可以使用 next/link 组件,传递 locale 属性,或者使用 useRouter 编程式地导航到不同 locale 的 URL。

示例 (next/link):

import Link from "next/link";
import { usePathname } from "next/navigation"; // App Router
// import { useRouter } from 'next/router'; // Pages Router

function LanguageSwitcher() {
const pathname = usePathname(); // App Router 获取当前路径 (不含 locale)
// const router = useRouter(); // Pages Router
// const { pathname, asPath, query, locale } = router;

return (
<nav>
<ul>
<li>
{/* App Router: 路径需要手动拼接 */}
<Link href={`/en${pathname}`} locale="en">
English
</Link>
</li>
<li>
<Link href={`/zh${pathname}`} locale="zh">
中文
</Link>
</li>
{/* Pages Router: Next.js 会自动处理路径 */}
{/*
<li><Link href={asPath} locale="en">English</Link></li>
<li><Link href={asPath} locale="fr">Français</Link></li>
*/}
</ul>
</nav>
);
}

SEO 优化 (SEO for i18n)

说明: 确保搜索引擎能正确索引不同语言版本的页面。

  • hreflang 标签: 告知搜索引擎页面存在哪些语言/区域变体。Next.js 的内置 i18n 路由或 next-intl 通常会自动处理 hreflang 链接的生成。
  • <html lang="...">: 正确设置每个页面的 lang 属性。
  • Locale-Specific Metadata: 为不同语言版本提供翻译后的 titledescription

本地化策略 (Localization Strategy - L10n)

说明: 本地化 (L10n) 不仅仅是翻译 (i18n),还包括适应目标市场的文化和习惯。

  • 日期/时间格式: 使用 Intl.DateTimeFormat
  • 数字/货币格式: 使用 Intl.NumberFormat
  • 排序: 注意不同语言的字母排序规则。
  • 图像/图标: 可能需要替换以适应当地文化。
  • 布局: 某些语言(如阿拉伯语)是从右到左书写,需要调整布局 (RTL support)。

示例 (日期格式化):

function FormattedDate({ date, locale }: { date: Date; locale: string }) {
const formatter = new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "long",
day: "numeric",
});
return <span>{formatter.format(date)}</span>;
}

// 使用: <FormattedDate date={new Date()} locale="zh-CN" />
// 输出可能为: "2023年10月27日"
// 使用: <FormattedDate date={new Date()} locale="en-US" />
// 输出可能为: "October 27, 2023"

2. 微前端 (Microfrontends)

说明: 将大型单体前端应用拆分成多个小型、独立、可独立部署的前端应用(微应用),然后将它们组合成一个整体。Next.js 可以作为构建微前端的 Shell 应用(宿主)或作为单个微应用。

Module Federation

说明: Webpack 5+ 的一个核心功能,允许 JavaScript 应用在运行时动态加载或共享其他独立构建的应用代码。是实现微前端的一种流行技术。@module-federation/nextjs-mf 插件可以帮助在 Next.js 中配置 Module Federation。

示例 (next.config.js - 概念性):

// next.config.js (Shell 应用)
const { NextFederationPlugin } = require("@module-federation/nextjs-mf");

const remotes = (isServer) => {
const location = isServer ? "ssr" : "chunks";
return {
// 定义远程微应用入口
// 'remoteApp' 是别名, 'remote_app_name' 是远程应用在 ModuleFederationPlugin 中定义的 name
// 'http://localhost:3001' 是远程应用的地址
// `_next/static/${location}/remoteEntry.js` 是远程入口文件的路径
remoteApp: `remote_app_name@http://localhost:3001/_next/static/${location}/remoteEntry.js`,
};
};

/** @type {import('next').NextConfig} */
const nextConfig = {
webpack(config, options) {
config.plugins.push(
new NextFederationPlugin({
name: "shellApp", // 当前应用 (Shell) 的名称
filename: "static/chunks/remoteEntry.js", // 当前应用的入口文件名
remotes: remotes(options.isServer), // 引入远程应用
exposes: {
// 可选:如果 Shell 也需要暴露组件给其他应用
// './nav': './components/nav',
},
shared: {
// 定义共享的依赖库,避免重复加载
// 'react': { singleton: true, requiredVersion: false },
// 'react-dom': { singleton: true, requiredVersion: false },
},
})
);
return config;
},
// ... 其他配置
};

module.exports = nextConfig;

微应用集成 (Micro App Integration)

说明: 将各个微应用组合到 Shell 应用中的方式。

  • iFrames: 简单但隔离性强,通信和样式共享困难。
  • Web Components: 将微应用封装成自定义元素。
  • Module Federation: 在运行时动态加载和集成。
  • Build-time Integration: 在构建时将微应用作为包引入 (通常在 Monorepo 中)。

路由集成 (Routing Integration)

说明: 协调 Shell 应用和微应用之间的路由。

  • Shell 驱动: Shell 应用处理所有顶层路由,根据路由加载相应的微应用。微应用内部可能还有自己的子路由。
  • 路由前缀: 为每个微应用分配 URL 前缀 (e.g., /app1/*, /app2/*)。
  • 事件/消息: 使用 Custom Events 或 postMessage (用于 iFrames) 进行路由通信。

状态共享 (State Sharing)

说明: 微应用之间或与 Shell 应用共享状态通常比较复杂,应尽量避免强耦合。

  • URL: 通过 URL 参数传递简单状态。
  • Props/Callbacks: Shell 向微应用传递数据和回调函数 (适用于 Module Federation)。
  • Custom Events: 使用浏览器自定义事件广播和监听状态变化。
  • localStorage/sessionStorage: 简单共享,但要注意命名空间冲突和大小限制。
  • 共享库/Context: 如果使用 Module Federation 共享同一个状态管理库实例。

部署策略 (Deployment Strategy)

说明: 微前端的核心优势是独立部署。

  • 独立部署: 每个微应用可以独立构建、测试和部署,不影响其他部分。
  • 版本管理: 需要策略来管理微应用和共享库的版本兼容性。
  • 环境一致性: 确保 Shell 和所有微应用使用的共享依赖版本一致(Module Federation 的 shared 配置)。

3. 服务端组件 (React Server Components - RSC)

说明: App Router 的核心特性,旨在优化性能和数据获取,是 React 的一个新范式。

React Server Components (RSC)

说明:

  • 运行环境: 仅在服务器端运行。
  • 输出: 生成特殊的 RSC Payload (一种虚拟 DOM 的描述) 或直接渲染为 HTML。
  • 能力: 可以直接访问后端资源(数据库、文件系统、API)、使用 async/await 获取数据、不能使用 Hooks (useState, useEffect)、不能使用浏览器 API (window, document)、不能添加交互事件监听器。
  • 默认: app 目录下的组件默认是 Server Components。

示例: (已在之前章节提供)

客户端组件 (Client Components)

说明:

  • 运行环境: 在服务器端预渲染 (SSR/SSG),然后在客户端 "激活" (Hydration) 并接管交互。
  • 标记: 文件顶部需要添加 'use client' 指令。
  • 能力: 可以使用 Hooks (useState, useEffect)、可以使用浏览器 API、可以添加事件监听器。
  • 限制: 不能直接 import Server Components (但可以接收 Server Components 作为 childrenprops)、数据获取通常通过客户端库 (SWR, React Query) 或 useEffect

示例: (已在之前章节提供)

混合渲染 (Hybrid Rendering)

说明: App Router 允许在同一个页面中混合使用 Server 和 Client Components。Server Components 可以导入并渲染 Client Components。可以将静态部分保留为 Server Components,将交互部分抽取为 Client Components。

示例 (Server 渲染 Client):

// app/page.tsx (Server Component)
import ClientButton from "@/components/ClientButton"; // 导入客户端组件
import ServerInfo from "@/components/ServerInfo"; // 导入另一个 Server Component (假设)

export default async function HomePage() {
return (
<div>
<h1>混合渲染示例</h1>
<ServerInfo /> {/* 渲染另一个 Server Component */}
<p>下面的按钮是一个客户端组件:</p>
<ClientButton initialCount={5} /> {/* 渲染客户端组件并传递 props */}
{/* 也可以将 Server Component 作为 children 传递给 Client Component */}
{/* <ClientWrapper> <ServerDataDisplay /> </ClientWrapper> */}
</div>
);
}

// components/ClientButton.tsx
("use client");
import { useState } from "react";

export default function ClientButton({
initialCount,
}: {
initialCount: number;
}) {
const [count, setCount] = useState(initialCount);
return (
<button onClick={() => setCount(count + 1)}>客户端计数器: {count}</button>
);
}

数据流 (Data Flow)

说明:

  • Server Components: 直接在组件内部使用 async/awaitfetch (或数据库客户端) 获取数据。获取的数据用于渲染或传递给 Client Components。
  • Client Components:
    • 通过 props 从父级 Server Component 接收数据。
    • 在客户端使用 useEffect, SWR, React Query 等方式获取数据。
    • 通过 Server Actions 调用服务端逻辑并更新状态。

性能优化 (Performance with RSC)

说明:

  • 减少客户端包体积: Server Components 的代码不发送到浏览器。
  • 优化数据获取: 在服务器端直接访问数据源,避免客户端请求瀑布。
  • 组件边界: 明智地划分 Server 和 Client 组件。将交互性尽可能地隔离在小的 Client Components (叶子节点) 中。避免不必要地将大型子树标记为 'use client'
  • 流式渲染 (Streaming): 与 Suspense 结合,服务器可以逐步发送 UI,改善可感知性能。