# 路由高级模式

> 路由系统的最后一章,覆盖 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` 强制每次执行

至此,第二篇「路由系统精讲」全部完成。下一篇将深入渲染模式与数据获取。