# 路由高级模式
> 路由系统的最后一章,覆盖 Route Handlers(API 路由)、流式响应、Webhook 处理、API 版本管理、以及 AI SaaS 项目中的 URL 设计最佳实践。这些是构建生产级 AI 全栈应用的关键路由能力。
## 1. Route Handlers 详解
### 1.1 基础结构
Route Handlers 是 App Router 的 API 路由方案,替代 Pages Router 的 `pages/api/`:
```tsx
// app/api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const chats = await db.query.chats.findMany()
return NextResponse.json(chats)
}
export async function POST(request: NextRequest) {
const body = await request.json()
const chat = await db.insert(chats).values(body).returning()
return NextResponse.json(chat[0], { status: 201 })
}
```
### 1.2 支持的 HTTP 方法
```tsx
// app/api/chat/[id]/route.ts
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const chat = await getChat(id)
if (!chat) {
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
return NextResponse.json(chat)
}
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const body = await request.json()
const updated = await updateChat(id, body)
return NextResponse.json(updated)
}
export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params
await deleteChat(id)
return new NextResponse(null, { status: 204 })
}
export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
// 部分更新
const { id } = await params
const body = await request.json()
const patched = await patchChat(id, body)
return NextResponse.json(patched)
}
```
### 1.3 请求解析
```tsx
export async function POST(request: NextRequest) {
// JSON 请求体
const json = await request.json()
// FormData
const formData = await request.formData()
const file = formData.get('file') as File
// URL 查询参数
const searchParams = request.nextUrl.searchParams
const page = searchParams.get('page')
// 请求头
const authHeader = request.headers.get('authorization')
// Cookies
const token = request.cookies.get('session')?.value
return NextResponse.json({ success: true })
}
```
### 1.4 响应类型
```tsx
// JSON 响应
return NextResponse.json({ data: chats })
// 自定义状态码和头
return NextResponse.json(
{ error: 'Unauthorized' },
{
status: 401,
headers: { 'X-Custom-Header': 'value' },
},
)
// 重定向
return NextResponse.redirect(new URL('/login', request.url))
// 纯文本
return new NextResponse('Hello World', {
headers: { 'Content-Type': 'text/plain' },
})
// 空响应
return new NextResponse(null, { status: 204 })
```
## 2. 流式响应:AI 对话核心
### 2.1 基础流式 API
AI 对话的核心——服务端流式返回文本:
```tsx
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText } from 'ai'
export async function POST(request: NextRequest) {
const { messages } = await request.json()
const result = streamText({
model: openai('gpt-4o'),
messages,
})
return result.toDataStreamResponse()
}
```
### 2.2 手动流式响应
不使用 AI SDK 时,手动创建 SSE 流:
```tsx
// app/api/stream/route.ts
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
const data = JSON.stringify({ count: i, time: Date.now() })
controller.enqueue(encoder.encode(`data: ${data}\n\n`))
await new Promise((resolve) => setTimeout(resolve, 1000))
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}
```
### 2.3 文件下载
```tsx
// app/api/export/route.ts
export async function GET(request: NextRequest) {
const chatId = request.nextUrl.searchParams.get('chatId')
const messages = await getChatMessages(chatId!)
const csv = messages
.map((m) => `${m.role},${m.content.replace(/,/g, ';')}`)
.join('\n')
return new NextResponse(csv, {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': `attachment; filename="chat-${chatId}.csv"`,
},
})
}
```
## 3. Webhook 处理
### 3.1 Stripe Webhook
```tsx
// app/api/webhook/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server'
import Stripe from 'stripe'
import { headers } from 'next/headers'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(request: NextRequest) {
const body = await request.text()
const headersList = await headers()
const signature = headersList.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!,
)
} catch (err) {
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 400 },
)
}
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(event.data.object)
break
case 'customer.subscription.updated':
await handleSubscriptionUpdated(event.data.object)
break
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object)
break
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object)
break
}
return NextResponse.json({ received: true })
}
```
::: warning Webhook 不要放在 Middleware 认证范围内
Webhook 来自第三方服务器,没有用户 Session。确保 Middleware 的 matcher 排除 `/api/webhook`:
```tsx
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|api/webhook).*)'],
}
```
:::
### 3.2 通用 Webhook 验证模式
```tsx
// lib/webhook.ts
import { createHmac } from 'crypto'
export function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
): boolean {
const expectedSignature = createHmac('sha256', secret)
.update(payload)
.digest('hex')
return `sha256=${expectedSignature}` === signature
}
```
## 4. Route Handler vs Server Action
何时用哪个?
| 场景 | Route Handler | Server Action |
|------|--------------|---------------|
| 外部 API 调用(Webhook) | ✅ | ❌ |
| 流式 AI 响应 | ✅ | ❌ |
| 文件上传/下载 | ✅ | ⚠️ 受限 |
| 表单提交 | ⚠️ 可以 | ✅ 更简洁 |
| 数据变更(CRUD) | ⚠️ 可以 | ✅ 更简洁 |
| 第三方 SDK 回调 | ✅ | ❌ |
| tRPC 入口 | ✅ | ❌ |
| 需要自定义 HTTP 头 | ✅ | ❌ |
**经验法则**:
- **表单/数据变更** → Server Action(更简洁,端到端类型安全)
- **流式 AI / Webhook / 文件 / 第三方集成** → Route Handler
## 5. URL 设计最佳实践
### 5.1 AI SaaS URL 结构
```
公开页面:
/ 首页
/pricing 定价
/blog 博客
/blog/:slug 博客文章
/changelog 更新日志
认证:
/login 登录
/register 注册
/forgot-password 忘记密码
应用(需要登录):
/chat 对话列表
/chat/new 新建对话
/chat/:id 对话详情
/knowledge 知识库列表
/knowledge/:id 知识库详情
/billing 账单
/settings 设置
/settings/team 团队管理
/settings/api-keys API Keys
管理后台(需要管理员):
/admin 管理后台
/admin/users 用户管理
/admin/analytics 数据分析
API:
/api/chat AI 对话(流式)
/api/webhook/stripe Stripe Webhook
/api/trpc/[trpc] tRPC 入口
```
### 5.2 URL 设计原则
1. **使用名词,不用动词**:`/chat/new` 而不是 `/create-chat`
2. **复数形式表示列表**:`/chats` 或 `/chat`(Next.js 社区偏好单数)
3. **层级清晰**:`/settings/team` 而不是 `/team-settings`
4. **避免深层嵌套**:最多 3 层 `/workspace/:id/chat/:chatId`
5. **查询参数用于筛选**:`/chat?model=gpt-4o&page=2`
### 5.3 API 版本管理
```
方案 1:路径前缀(推荐)
app/api/v1/chat/route.ts → /api/v1/chat
app/api/v2/chat/route.ts → /api/v2/chat
方案 2:请求头
X-API-Version: 2
方案 3:查询参数
/api/chat?version=2
```
路径前缀方案的实现:
```tsx
// app/api/v1/chat/route.ts
export async function GET() {
// v1 逻辑
return NextResponse.json({ version: 1, data: [] })
}
// app/api/v2/chat/route.ts
export async function GET() {
// v2 逻辑,返回不同的数据结构
return NextResponse.json({ version: 2, items: [], meta: {} })
}
```
## 6. Route Handler 缓存与性能
### 6.1 静态 vs 动态
```tsx
// 默认静态(构建时执行一次,结果被缓存)
export async function GET() {
const data = await fetchStaticData()
return NextResponse.json(data)
}
// 使用动态 API 后变为动态(每次请求都执行)
export async function GET(request: NextRequest) {
const token = request.headers.get('authorization') // 读取请求头 → 动态
return NextResponse.json({ data: [] })
}
```
强制动态:
```tsx
export const dynamic = 'force-dynamic'
export async function GET() {
// 每次请求都执行
}
```
### 6.2 CORS 配置
```tsx
// app/api/chat/route.ts
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export async function OPTIONS() {
return new NextResponse(null, { headers: corsHeaders })
}
export async function GET(request: NextRequest) {
const data = await getChats()
return NextResponse.json(data, { headers: corsHeaders })
}
```
## 本章小结
- **Route Handlers**:支持所有 HTTP 方法,替代 Pages Router 的 `pages/api/`
- **流式响应**:AI 对话的核心——`streamText` + `toDataStreamResponse()` 实现 SSE
- **Webhook**:Stripe 等第三方回调,注意排除认证 Middleware、验证签名
- **Route Handler vs Server Action**:流式/Webhook/文件 用 Route Handler,表单/CRUD 用 Server Action
- **URL 设计**:名词路径、层级清晰、查询参数筛选、路径前缀版本管理
- **缓存**:读取 request 参数自动变为动态,`force-dynamic` 强制每次执行
至此,第二篇「路由系统精讲」全部完成。下一篇将深入渲染模式与数据获取。