phase 5 part 1: typescript, vue, react, ssr(Next.js 16) topics with lessons, scenarios, interviews, verified examples
This commit is contained in:
1 parent
7a32d2c9a6
commit
7b5cae7d24
39 files changed
+2482
-48
No files matched your search
@@ -0,0 +1,114 @@
|
||||
---
|
||||
id: frontend.ssr.nextjs-app-router
|
||||
title: Next.js App Router:服务端组件、客户端组件、路由处理器做 AI 流式接口、缓存
|
||||
type: lesson
|
||||
domain: frontend
|
||||
stack: [ssr, react]
|
||||
tags: [Next.js, App Router, Server Components, use client, Route Handlers, Server Actions, 流式响应, Turbopack]
|
||||
level: intermediate
|
||||
prerequisites: [frontend.react.hooks-rendering, frontend.ssr.rendering-modes]
|
||||
est_hours: 3
|
||||
related: [frontend.ssr.scenario.help-center, backend.nodejs.index, frontend.ssr.interview]
|
||||
week: [7]
|
||||
order: 2
|
||||
---
|
||||
|
||||
# Next.js App Router:服务端组件、客户端组件、路由处理器做 AI 流式接口、缓存
|
||||
|
||||
> 基于 Next.js 16 编写并实际构建通过。Next.js 大版本之间变化很多(例如 16 版默认使用 Turbopack 构建,`middleware` 文件约定改名为 `proxy`,缓存机制也在调整),以官方文档为准:https://nextjs.org/docs 。BOSS直聘 2026-10-23 的调研中有岗位提到 Next.js,它也是“前端出身做全栈”最常见的选择。
|
||||
|
||||
## 解释说明
|
||||
|
||||
### App Router 的核心概念
|
||||
|
||||
| 概念 | 说明 |
|
||||
| --- | --- |
|
||||
| 文件路由 | `app/` 目录:`page.tsx` 页面、`layout.tsx` 布局、`loading.tsx` 加载态、`error.tsx` 错误边界、`route.ts` 接口 |
|
||||
| **服务端组件**(默认) | 在服务器上运行,可以直接访问数据库 / 密钥,代码不会发到浏览器;不能用 `useState`、事件处理 |
|
||||
| **客户端组件** | 文件顶部写 `"use client"`,可以有状态和交互;它和它导入的模块会打包到浏览器 |
|
||||
| 路由处理器 | `app/api/xxx/route.ts` 导出 `GET` / `POST` 等函数,使用 Web 标准的 `Request` / `Response`,可以返回流 |
|
||||
| Server Actions | `"use server"` 的异步函数,可以直接作为表单的 `action`,在服务端执行 |
|
||||
| 缓存和重新验证 | 数据请求和页面的缓存策略(`revalidate`、按路径 / 标签失效等),不同版本默认值不同,以官方文档为准 |
|
||||
|
||||
**边界原则**:尽量让组件保持为服务端组件,只把需要交互的“叶子”做成客户端组件,减少浏览器端的 JS;不能把函数、类实例作为 props 从服务端组件传给客户端组件(必须可序列化)。
|
||||
|
||||
### 用 Next.js 做 AI 接口
|
||||
|
||||
路由处理器中调用模型的流式接口,返回 `ReadableStream`;模型密钥只在服务端;用 `request.signal` 感知用户断开并停止生成。复杂的 Agent 逻辑仍然建议放在 Python 服务中,Next.js 作为 BFF(面向前端的后端)转发和鉴权。
|
||||
|
||||
## 案例
|
||||
|
||||
帮助中心:常见问题由服务端组件渲染(构建时静态生成,SEO 好);智能客服是客户端组件,调用 `/api/chat` 路由处理器,后者流式返回答案。构建结果中 `/` 是静态页面,`/api/chat` 是按需执行的动态路由。
|
||||
|
||||
## 代码示例
|
||||
|
||||
完整项目:[examples/next-ai-chat](examples/next-ai-chat/)(`npm install && npm run build` 已构建通过,`next start` 后实际请求验证)。
|
||||
|
||||
路由处理器([app/api/chat/route.ts](examples/next-ai-chat/app/api/chat/route.ts)):
|
||||
|
||||
```ts
|
||||
export async function POST(request: Request): Promise<Response> {
|
||||
const { question } = (await request.json()) as { question?: unknown };
|
||||
if (typeof question !== "string" || question.length === 0 || question.length > 500) {
|
||||
return Response.json({ error: "question 必须是 1~500 字的字符串" }, { status: 400 });
|
||||
}
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
try {
|
||||
for await (const token of fakeLLM(question, request.signal)) { // signal:用户断开就停止
|
||||
controller.enqueue(encoder.encode(token));
|
||||
}
|
||||
} finally {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
return new Response(stream, { headers: { "Content-Type": "text/plain; charset=utf-8", "X-Accel-Buffering": "no" } });
|
||||
}
|
||||
```
|
||||
|
||||
服务端组件页面 + 客户端组件([app/page.tsx](examples/next-ai-chat/app/page.tsx)、[app/chat.tsx](examples/next-ai-chat/app/chat.tsx)):
|
||||
|
||||
```tsx
|
||||
// app/page.tsx —— 服务端组件:取数和渲染都在服务器
|
||||
export default async function Page() {
|
||||
const faqs = await getFaqs();
|
||||
return (
|
||||
<main>
|
||||
{faqs.map((f) => <details key={f.id}><summary>{f.q}</summary><p>{f.a}</p></details>)}
|
||||
<Chat /> {/* 客户端组件 */}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
// app/chat.tsx
|
||||
"use client";
|
||||
export function Chat() { const [answer, setAnswer] = useState(""); /* fetch + 读取流 */ }
|
||||
```
|
||||
|
||||
构建输出:
|
||||
|
||||
```text
|
||||
Route (app)
|
||||
┌ ○ /
|
||||
├ ○ /_not-found
|
||||
└ ƒ /api/chat
|
||||
○ (Static) prerendered as static content
|
||||
ƒ (Dynamic) server-rendered on demand
|
||||
```
|
||||
|
||||
## 易错点
|
||||
|
||||
- 在服务端组件中使用 `useState` 或事件处理,或者忘了写 `"use client"`。
|
||||
- 把整个页面都标记为 `"use client"`,失去服务端组件的好处。
|
||||
- 在客户端组件中导入了只能在服务端使用的模块(数据库客户端、密钥)。
|
||||
- 以为缓存行为和上个版本一样:升级大版本前阅读升级指南。
|
||||
- 部署在 Nginx 后面时流式响应被缓冲。
|
||||
- 把复杂的 Agent 长任务放在路由处理器中执行,受函数超时限制(尤其是 Serverless 部署)。
|
||||
|
||||
## 面试常问
|
||||
|
||||
- 服务端组件和客户端组件的区别?边界怎么划分?
|
||||
- Next.js 的路由处理器和 Server Actions 分别用在哪里?
|
||||
- 用 Next.js 做 AI 应用的流式接口要注意什么?
|
||||
Reference in new issue
Block a user