一、概览与提问(SQ3R · Survey & Question)

SQ3R 第一步:快速浏览全貌,提出关键问题。

什么是 Next.js?

Next.js 是由 Vercel 开发和维护的 React 全栈框架。它不仅仅是一个前端库的封装,而是一套完整的应用开发解决方案——从路由、渲染、数据获取到部署,覆盖了 Web 应用开发的全部生命周期。

Next.js 诞生的背景很明确:React 本身只是一个 UI 库,要构建一个完整的 Web 应用,开发者还需要自己处理路由、服务端渲染、代码分割、数据获取等一系列问题。Next.js 的出现就是为了填补这些空白,让开发者能用 React 快速构建生产级别的应用。

当前最新版本为 Next.js 16(基于文档版本 16.2.6),引入了 Cache ComponentsPartial Prerendering(PPR) 等重大特性,架构模型发生了根本性变化。

核心问题

  • Next.js 适合什么场景?——从静态博客到动态 SaaS 平台,从内容站到电商平台,Next.js 都能胜任
  • Next.js 与同类技术相比有什么优势?——内置 SSR/SSG/ISR、文件系统路由、自动代码分割、React Server Components 原生支持
  • 学习 Next.js 需要什么基础?——HTML、CSS、JavaScript 和 React 基本知识

技术全景图

Next.js 的架构可以看作一棵分层树:

Next.js 应用
├── App Router (/app)              ← 推荐的新路由系统
│   ├── 布局系统 (layout.js)       ← 共享布局,不会重新渲染
│   ├── 页面系统 (page.js)         ← 路由入口,每个路由一个
│   ├── 加载状态 (loading.js)      ← 自动 Suspense 边界
│   ├── 错误处理 (error.js)        ← 错误边界
│   ├── API 路由 (route.js)        ← 服务端 API 端点
│   └── 特殊文件 (not-found.js 等)
├── Server Components              ← 默认,服务端渲染,零 JS 发送到客户端
├── Client Components ("use client") ← 客户端交互,需要时使用
├── Cache Components ("use cache")   ← Next.js 16 新增,缓存策略
├── 数据获取 (fetch / ORM)         ← Server Components 中直接 await
├── Server Actions ("use server")  ← 服务端函数,表单提交
└── next.config.ts                 ← 全局配置

二、用最简单的话说清楚(费曼学习法)

费曼学习法核心理念:如果你不能用简单的语言解释一件事,说明你还没有真正理解它。

核心概念讲解

1. 文件系统路由

Next.js 的路由完全由文件夹结构决定。你在 app/ 目录下创建一个文件夹,它就是一个路由。文件夹里放一个 page.js,它就是这个路由的页面。

// app/page.tsx → 对应路由 /
export default function HomePage() {
  return <h1>这是首页</h1>;
}
 
// app/about/page.tsx → 对应路由 /about
export default function AboutPage() {
  return <h1>关于我们</h1>;
}
 
// app/blog/[id]/page.tsx → 对应路由 /blog/123(动态路由)
export default async function BlogPost({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  return <h1>文章 {id}</h1>;
}

不需要写任何路由配置文件,文件夹结构就是路由表。

2. 布局(Layout)

布局是一个共享的外壳,它包裹着所有子页面。最关键的是:导航时布局不会重新渲染

// app/layout.tsx → 根布局(必须有 html 和 body 标签)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>
        <nav>导航栏(导航时不重新渲染)</nav>
        <main>{children}</main>
      </body>
    </html>
  );
}

3. Server Components 和 Client Components

这是 Next.js 最核心的概念。默认情况下,所有组件都是 Server Components——它们在服务端运行,不会发送任何 JavaScript 到客户端。当你需要交互(点击事件、状态管理、浏览器 API)时,才使用 Client Components

// Server Component(默认)- 在服务端运行,发送 HTML 到客户端
// app/page.tsx
import { db } from "@/lib/db";
import LikeButton from "./like-button";
 
export default async function Page() {
  const posts = await db.query("SELECT * FROM posts"); // 直接访问数据库
  return (
    <div>
      {posts.map((post) => (
        <p key={post.id}>{post.title}</p>
      ))}
      <LikeButton /> {/* 交互部分交给 Client Component */}
    </div>
  );
}
// Client Component - 需要交互时添加 "use client"
// app/like-button.tsx
"use client";
 
import { useState } from "react";
 
export default function LikeButton() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>赞 {count}</button>;
}

4. 数据获取

在 Server Components 中,数据获取变得极其简单——直接用 async/await

// Server Component 中直接 fetch
export default async function BlogPage() {
  const res = await fetch("https://api.example.com/posts");
  const posts = await res.json();
 
  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

不需要 useEffect,不需要 useState,不需要客户端数据获取库。代码就像在写普通的异步函数。

5. 缓存(Next.js 16 新特性)

Next.js 16 引入了 Cache Components,通过 "use cache" 指令让缓存变得声明式。

// next.config.ts 中启用
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  cacheComponents: true,
};
export default nextConfig;
// 数据级缓存
import { cacheLife } from "next/cache";
 
export async function getUsers() {
  "use cache";
  cacheLife("hours"); // 缓存一小时
  return db.query("SELECT * FROM users");
}
// UI 级缓存
import { cacheLife } from "next/cache";
 
export default async function BlogPosts() {
  "use cache";
  cacheLife("hours");
  const posts = await fetch("https://api.example.com/posts");
  return <div>{/* 渲染文章列表 */}</div>;
}

6. Server Actions

Server Actions 让你直接在组件中定义服务端函数,无需手动写 API 路由。

// app/actions.ts
"use server";
 
import { revalidateTag } from "next/cache";
 
export async function createPost(formData: FormData) {
  await db.post.create({
    data: { title: formData.get("title") as string },
  });
  revalidateTag("posts"); // 刷新缓存
}
// 在表单中使用
import { createPost } from "./actions";
 
export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="文章标题" />
      <button type="submit">发布</button>
    </form>
  );
}

类比与比喻

把 Next.js 应用想象成一家餐厅

  • Server Components = 后厨厨师——在厨房里准备好菜品(HTML),客人看不到烹饪过程,只能看到端上桌的成品。好处是厨房里可以用大型设备(数据库、文件系统),客人不需要在自己的桌上放这些设备。
  • Client Components = 桌上的调料瓶——需要客人自己动手(交互),比如加酱油(点击按钮)、搅拌(拖动)。
  • Layout = 餐厅装修——不管换哪道菜(页面导航),装修都不会变,不需要重新装修。
  • Cache Components("use cache") = 预制菜——提前做好,客人点了直接上,不用现做。
  • Streaming(Suspense) = 先上凉菜再上热菜——不必等所有菜做好才上桌,做好一道上一道。
  • Server Actions = 服务员点单系统——客人只需填表,服务员自动传到后厨处理。

常见误解澄清

  1. 误解:Next.js 只能做 SSR —— 事实上它支持 SSG、SSR、ISR、CSR 和 PPR(Partial Prerendering),可以在同一个应用中混合使用。
  2. 误解:所有组件都需要 "use client" —— 恰恰相反,默认都是 Server Components,只有需要交互时才加 "use client"
  3. 误解:Server Components 不能传 props 给 Client Components —— 完全可以,只要 props 是可序列化的(数字、字符串、对象等)。
  4. 误解:Next.js 16 还在用 Pages Router 的 getServerSideProps —— App Router 已经完全不同,数据获取直接 await,不需要这些函数。
  5. 误解:缓存很难控制 —— Next.js 16 的 "use cache" + cacheLife + cacheTag 让缓存变得声明式和可控。

三、锥形深入(西蒙学习法)

西蒙学习法:集中精力、目标导向、锥形深入——从核心开始,逐步扩展到周边。

第一层:核心基础

1. 创建项目

npx create-next-app@latest

安装时会提示选择 TypeScript、ESLint、Tailwind CSS、src/ 目录、App Router 等。

手动安装:

npm install next@latest react@latest react-dom@latest
// package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

2. 项目结构

my-app/
├── app/
│   ├── layout.tsx          ← 根布局
│   ├── page.tsx            ← 首页 (/)
│   ├── blog/
│   │   ├── page.tsx        ← /blog
│   │   └── [id]/
│   │       └── page.tsx    ← /blog/123
│   ├── about/
│   │   └── page.tsx        ← /about
│   └── api/
│       └── route.ts        ← API 端点
├── public/                 ← 静态资源
├── next.config.ts          ← 配置文件
├── package.json
└── tsconfig.json

3. 页面和布局

// app/layout.tsx - 根布局(必需)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>{children}</body>
    </html>
  );
}
// app/blog/layout.tsx - 嵌套布局
export default function BlogLayout({ children }: { children: React.ReactNode }) {
  return (
    <section>
      <h2>博客</h2>
      {children}
    </section>
  );
}

4. 链接和导航

import Link from "next/link";
 
export default function NavBar() {
  return (
    <nav>
      <Link href="/">首页</Link>
      <Link href="/about">关于</Link>
      <Link href="/blog/123">文章</Link>
    </nav>
  );
}

<Link> 组件会自动预取页面,实现即时导航。

5. 动态路由

// app/blog/[id]/page.tsx
export default async function BlogPost({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  return <h1>文章 ID: {id}</h1>;
}

Catch-all 路由:[...slug] 匹配多个路径段,[[...slug]] 是可选的 catch-all。

6. 加载状态

// app/blog/loading.tsx - 自动 Suspense 边界
export default function Loading() {
  return <div>加载中...</div>;
}

blog/page.tsx 在服务端获取数据时,用户会立即看到 "加载中...",数据就绪后自动替换。

7. 错误处理

// app/error.tsx - 必须是 Client Component
"use client";
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>出错了!</h2>
      <button onClick={reset}>重试</button>
    </div>
  );
}

8. API 路由(Route Handlers)

// app/api/posts/route.ts
import { NextResponse } from "next/server";
 
export async function GET() {
  const posts = await db.query("SELECT * FROM posts");
  return NextResponse.json(posts);
}
 
export async function POST(request: Request) {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return NextResponse.json(post, { status: 201 });
}

第二层:进阶用法

1. Server Components 和 Client Components 的组合模式

最常见的模式是:Server Component 获取数据,通过 props 传递给 Client Component 处理交互。

// app/page.tsx(Server Component)
import LikeButton from "./like-button";
import { getPost } from "@/lib/data";
 
export default async function Page() {
  const post = await getPost("1");
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <LikeButton likes={post.likes} />
    </article>
  );
}

还可以通过 children prop 将 Server Component 嵌套在 Client Component 中:

// app/ui/modal.tsx(Client Component)
"use client";
 
export default function Modal({ children }: { children: React.ReactNode }) {
  return <div className="modal">{children}</div>;
}
// app/page.tsx(Server Component)
import Modal from "./ui/modal";
import Cart from "./ui/cart"; // Cart 是 Server Component
 
export default function Page() {
  return (
    <Modal>
      <Cart /> {/* Server Component 作为 children 传入 Client Component */}
    </Modal>
  );
}

2. Context Providers

React Context 不支持 Server Components,需要在 Client Component 中创建 Provider:

// app/theme-provider.tsx
"use client";
 
import { createContext } from "react";
 
export const ThemeContext = createContext("light");
 
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>;
}
// app/layout.tsx
import ThemeProvider from "./theme-provider";
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

3. Streaming 和 Suspense

Suspense 是 Next.js 中流式渲染的核心。它让你可以把页面拆分成多个独立加载的块:

import { Suspense } from "react";
 
export default function Page() {
  return (
    <>
      <h1>我的博客</h1>
      {/* 这部分立即显示 */}
      <p>欢迎来到我的博客</p>
      {/* 这部分在数据加载完成后流式显示 */}
      <Suspense fallback={<div>加载文章中...</div>}>
        <BlogPosts />
      </Suspense>
      <Suspense fallback={<div>加载评论中...</div>}>
        <Comments />
      </Suspense>
    </>
  );
}

4. 数据获取模式

并行获取(推荐):

// 多个请求同时发起
export default async function Page() {
  const artistPromise = getArtist();
  const albumsPromise = getAlbums();
 
  const [artist, albums] = await Promise.all([artistPromise, albumsPromise]);
 
  return (
    <div>
      <h1>{artist.name}</h1>
      <AlbumList albums={albums} />
    </div>
  );
}

顺序获取(当请求有依赖关系时):

export default async function Page() {
  const artist = await getArtist(); // 先获取 artist
  const playlists = await getPlaylists(artist.id); // 再用 artist.id 获取
  return <div>{/* ... */}</div>;
}

5. 缓存与 Revalidation(Next.js 16)

Next.js 16 的缓存系统基于 "use cache" 指令:

import { cacheLife, cacheTag } from "next/cache";
 
// 数据级缓存 + 标签
export async function getProducts() {
  "use cache";
  cacheLife("hours"); // 缓存存活时间
  cacheTag("products"); // 缓存标签,用于按需刷新
  return db.query("SELECT * FROM products");
}

时间基准刷新

"use cache";
cacheLife("seconds"); // 每秒刷新
cacheLife("minutes"); // 每分钟刷新
cacheLife("hours"); // 每小时刷新
cacheLife("days"); // 每天刷新
cacheLife("weeks"); // 每周刷新
cacheLife("max"); // 最大缓存

按需刷新

// app/actions.ts
"use server";
import { revalidateTag, revalidatePath, updateTag } from "next/cache";
 
export async function refreshProducts() {
  revalidateTag("products"); // 刷新带 'products' 标签的所有缓存
}
 
export async function refreshPage() {
  revalidatePath("/products"); // 刷新 /products 路径的所有缓存
}
 
export async function updateProducts() {
  updateTag("products"); // 立即过期并重新生成
}

6. 运行时 API 与缓存

运行时 API(cookies()headers()searchParams)需要请求级别的信息。在 Next.js 16 中,使用它们的组件需要用 <Suspense> 包裹:

import { cookies } from "next/headers";
import { Suspense } from "react";
 
async function UserGreeting() {
  const cookieStore = await cookies();
  const theme = cookieStore.get("theme")?.value || "light";
  return <p>你的主题: {theme}</p>;
}
 
export default function Page() {
  return (
    <>
      <h1>仪表盘</h1>
      <Suspense fallback={<p>加载中...</p>}>
        <UserGreeting />
      </Suspense>
    </>
  );
}

7. Metadata 和 SEO

// app/layout.tsx - 静态 metadata
export const metadata = {
  title: "我的网站",
  description: "这是我的网站描述",
};
 
// app/blog/[id]/page.tsx - 动态 metadata
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const post = await getPost(id);
  return {
    title: post.title,
    description: post.excerpt,
  };
}

第三层:深度解析

1. 渲染架构:Partial Prerendering(PPR)

Next.js 16 的默认渲染模式是 Partial Prerendering。在构建时,Next.js 会预渲染整个组件树,根据每个组件使用的 API 来决定如何处理:

  • "use cache" 的组件 → 结果被缓存,包含在静态 Shell 中
  • <Suspense> 包裹的组件 → fallback 包含在静态 Shell 中,内容在请求时流式传输
  • 纯确定性操作 → 自动包含在静态 Shell 中

这意味着用户访问页面时,会立即看到静态的页面框架(导航栏、布局、缓存的内容),然后个性化内容通过流式传输逐步加载。

2. RSC Payload

React Server Components 渲染后会生成一种特殊的二进制格式——RSC Payload。它包含:

  • Server Components 的渲染结果
  • Client Components 的占位符和 JS 文件引用
  • 从 Server Component 传递给 Client Component 的 props

在首次加载时,HTML 提供即时预览,RSC Payload 用于协调组件树,JavaScript 用于水合 Client Components。后续导航则直接使用 RSC Payload,不需要重新获取 HTML。

3. 环境隔离与安全

Next.js 在服务端和客户端之间建立了严格的安全边界:

  • 环境变量只有 NEXT_PUBLIC_ 前缀的才会暴露给客户端
  • 即使不慎在客户端导入了服务端代码,敏感变量也会被替换为空字符串
  • 可以使用 server-only 包在构建时报错阻止客户端导入服务端代码
  • 可以使用 client-only 包阻止服务端导入客户端代码
import "server-only"; // 阻止客户端导入
 
export async function getData() {
  const res = await fetch("https://api.example.com/data", {
    headers: { authorization: process.env.API_KEY },
  });
  return res.json();
}

4. 减小客户端 JS 体积的最佳实践

// 正确做法:只在需要交互的组件上加 "use client"
// app/layout.tsx - 这是 Server Component
import Search from "./search"; // Client Component
import Logo from "./logo"; // Server Component
 
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Logo />
        <Search /> {/* 只有搜索栏是 Client Component */}
      </nav>
      <main>{children}</main>
    </>
  );
}

"use client" 尽可能下推到叶子组件,减少客户端 JavaScript 的体积。

5. 共享数据的模式:React.cache + Context

在多个组件间共享数据而不重复请求:

import { cache, createContext } from "react";
 
// 创建缓存函数,同一请求内多次调用只执行一次
const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

React.cache 的作用域是单个请求,不同请求之间不会共享缓存。

6. 第三方组件集成

对于没有 "use client" 指令的第三方组件,创建一个包装文件:

// app/carousel.tsx
"use client";
import { Carousel } from "acme-carousel";
export default Carousel;

然后在 Server Component 中直接导入这个包装后的组件即可。

7. 路由段配置

// app/api/route.ts
export const dynamic = "force-dynamic"; // 强制动态渲染
export const revalidate = 0; // 禁用缓存
export const runtime = "edge"; // 使用 Edge Runtime
export const maxDuration = 60; // 最大执行时间(秒)

四、要点笔记(康奈尔笔记法)

康奈尔笔记法:将笔记分为线索栏、笔记栏和总结栏,便于复习和检索。

关键概念速查表

线索/关键词详细笔记
App Router基于 /app 目录的路由系统,支持嵌套布局、Server Components
Server Components默认组件类型,服务端渲染,不发送 JS 到客户端,可直接访问数据库
Client Components添加 "use client" 指令,用于交互、状态管理、浏览器 API
Cache ComponentsNext.js 16 新增,"use cache" 指令实现声明式缓存
PPR (Partial Prerendering)默认渲染模式,静态 Shell + 流式动态内容
Server Actions"use server" 定义的服务端函数,可直接在表单中使用
RSC PayloadServer Components 渲染后的二进制格式,用于客户端协调
Streaming通过 <Suspense> 实现流式渲染,先显示 fallback 再加载内容
cacheLife配合 "use cache" 设置缓存存活时间(seconds/minutes/hours 等)
cacheTag为缓存设置标签,配合 revalidateTag / updateTag 按需刷新
文件系统路由文件夹结构即路由,page.js 定义页面,layout.js 定义布局
generateStaticParams为动态路由预生成静态参数,用于 SSG

核心 API 速查

API / 配置用途示例
"use client"声明 Client Component'use client' 加在文件顶部
"use server"声明 Server Action'use server' 加在函数顶部或文件顶部
"use cache"声明缓存(需启用 cacheComponents)'use cache' 加在函数/组件顶部
cacheLife(profile)设置缓存策略cacheLife('hours')
cacheTag(tag)为缓存打标签cacheTag('products')
revalidateTag(tag)按标签刷新缓存revalidateTag('products')
revalidatePath(path)按路径刷新缓存revalidatePath('/blog')
updateTag(tag)立即过期并重新生成缓存updateTag('posts')
cookies()读取请求 cookiesconst store = await cookies()
headers()读取请求 headersconst h = await headers()
redirect(path)重定向redirect('/login')
notFound()触发 404 页面notFound()
<Link href>客户端导航,自动预取<Link href="/about">关于</Link>
<Image>图片优化<Image src="/photo.jpg" alt="..." width={500} height={300} />
<Suspense>流式渲染边界<Suspense fallback={<Loading />}><Component /></Suspense>
generateMetadata动态生成页面 metadataexport async function generateMetadata() { ... }
generateStaticParams预生成动态路由参数export async function generateStaticParams() { ... }
connection()标记需要请求级信息的代码await connection() 后使用 Date.now()

本节总结

Next.js 16 是一个基于 React 的全栈框架,核心围绕 App Router 构建应用。默认使用 Server Components 实现零 JS 的服务端渲染,需要交互时通过 "use client" 引入 Client Components。Next.js 16 引入的 Cache Components"use cache")和 Partial Prerendering 极大地简化了缓存策略,将渲染模型统一为"静态 Shell + 流式动态内容"。数据获取直接在 Server Components 中 async/await,无需客户端数据获取库。Server Actions 通过 "use server" 让表单提交和 mutations 回归简单。


五、复习与实践(SQ3R · Recite & Review)

SQ3R 最后两步:复述核心要点,通过实践巩固理解。

核心要点回顾

  1. 文件系统路由app/ 目录下的文件夹结构就是路由表,page.js 是路由入口,layout.js 是共享布局
  2. Server Components 是默认的:不需要任何指令,组件在服务端渲染,不发送 JS 到客户端
  3. "use client" 是边界:标记了客户端和服务端的分界线,该文件及其所有导入都会打包到客户端
  4. 布局不重新渲染:导航时 layout.js 保持不变,只有 page.js 的内容会更新
  5. 数据获取用 async/await:Server Components 中直接 await fetch 或 ORM 查询,不需要 useEffect
  6. "use cache" + cacheLife:Next.js 16 的声明式缓存方案,替代了之前的 fetch 缓存选项
  7. Streaming 是核心模式:用 <Suspense> 包裹异步内容,先显示 fallback,数据就绪后流式替换
  8. Server Actions 简化 mutations"use server" 定义服务端函数,直接绑定到表单的 action
  9. PPR 是默认渲染模式:静态内容预渲染,动态内容流式传输,最佳性能组合
  10. 环境安全:非 NEXT_PUBLIC_ 前缀的环境变量不会暴露给客户端,server-only 包提供额外保护

动手练习

练习 1:个人博客(入门级)

创建一个简单的博客应用:

  • 首页展示文章列表
  • 文章详情页使用动态路由 [slug]
  • 添加 loading.tsxerror.tsx
  • 使用 generateStaticParams 预生成页面

练习 2:带缓存的商品列表(中级)

构建一个商品展示页:

  • 启用 cacheComponents: true
  • 使用 "use cache" + cacheLife('hours') 缓存商品数据
  • 使用 cacheTag('products') 为缓存打标签
  • 创建 Server Action 实现 updateTag('products') 刷新缓存

练习 3:全栈仪表盘(高级)

创建一个带认证的仪表盘:

  • 使用 cookies() 读取 session
  • <Suspense> 包裹个性化内容,实现流式加载
  • Server Components 获取数据,Client Components 处理图表交互
  • 使用 Context Provider 共享主题状态

常见陷阱

  1. 在 Server Component 中使用 useStateonClick —— 这些是客户端 API,必须添加 "use client"。解法:拆分为 Server + Client Components。
  2. 在 Client Component 中直接访问数据库 —— Client Component 在浏览器运行,无法访问服务端资源。解法:在 Server Component 中获取数据,通过 props 传递。
  3. 在 layout 中使用 cookies() 却没有 Suspense 边界 —— Next.js 16 中,运行时 API 访问会导致构建错误。解法:将运行时数据访问提取到子组件中并用 <Suspense> 包裹。
  4. 忘记 await params —— Next.js 16 中 params 是 Promise 类型,必须 await 后才能使用。
  5. 过度使用 "use client" —— 把整个页面标记为 Client Component 会导致大量 JS 发送到客户端。解法:只在最小粒度的交互组件上使用。

延伸阅读