Next.js Route Handlers #
一、Route Handlers概述 #
1.1 什么是Route Handlers #
Route Handlers是App Router中的API路由,使用Web标准Request和Response对象:
tsx
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello' })
}
1.2 与Pages Router的区别 #
| 特性 | App Router | Pages Router |
|---|---|---|
| 文件 | route.ts | pages/api/*.ts |
| 请求对象 | Request | NextApiRequest |
| 响应对象 | Response | NextApiResponse |
| 运行时 | Node.js/Edge | Node.js |
二、流式响应 #
2.1 基本流式响应 #
tsx
import { NextResponse } from 'next/server'
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const messages = ['Hello', ' ', 'World', '!']
for (const message of messages) {
controller.enqueue(encoder.encode(message))
await new Promise(resolve => setTimeout(resolve, 100))
}
controller.close()
},
})
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/plain',
'Transfer-Encoding': 'chunked',
},
})
}
2.2 Server-Sent Events #
tsx
import { NextResponse } from 'next/server'
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
let count = 0
const interval = setInterval(() => {
const data = JSON.stringify({ time: new Date(), count: count++ })
controller.enqueue(encoder.encode(`data: ${data}\n\n`))
}, 1000)
setTimeout(() => {
clearInterval(interval)
controller.close()
}, 30000)
},
})
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
})
}
2.3 AI流式响应 #
tsx
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const { prompt } = await request.json()
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
stream: true,
}),
})
const reader = response.body?.getReader()
if (reader) {
while (true) {
const { done, value } = await reader.read()
if (done) break
controller.enqueue(value)
}
}
controller.close()
},
})
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
},
})
}
三、文件处理 #
3.1 文件上传 #
tsx
import { NextRequest, NextResponse } from 'next/server'
import { writeFile } from 'fs/promises'
import path from 'path'
export async function POST(request: NextRequest) {
const formData = await request.formData()
const file = formData.get('file') as File
if (!file) {
return NextResponse.json({ error: 'No file uploaded' }, { status: 400 })
}
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const filename = `${Date.now()}-${file.name}`
const filepath = path.join(process.cwd(), 'public', 'uploads', filename)
await writeFile(filepath, buffer)
return NextResponse.json({
success: true,
filename,
url: `/uploads/${filename}`,
})
}
3.2 多文件上传 #
tsx
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const formData = await request.formData()
const files = formData.getAll('files') as File[]
const uploadedFiles = []
for (const file of files) {
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const filename = `${Date.now()}-${file.name}`
await writeFile(`public/uploads/${filename}`, buffer)
uploadedFiles.push({
filename,
originalName: file.name,
size: file.size,
})
}
return NextResponse.json({ files: uploadedFiles })
}
3.3 文件下载 #
tsx
import { NextResponse } from 'next/server'
import { readFile } from 'fs/promises'
import path from 'path'
export async function GET(request: Request, { params }: { params: Promise<{ filename: string }> }) {
const { filename } = await params
const filepath = path.join(process.cwd(), 'public', 'uploads', filename)
try {
const file = await readFile(filepath)
return new NextResponse(file, {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${filename}"`,
},
})
} catch {
return NextResponse.json({ error: 'File not found' }, { status: 404 })
}
}
四、Edge Runtime #
4.1 配置Edge Runtime #
tsx
export const runtime = 'edge'
export async function GET() {
return new Response('Hello from Edge!')
}
4.2 Edge Runtime特点 #
| 特性 | Node.js Runtime | Edge Runtime |
|---|---|---|
| 启动时间 | 较慢 | 极快 |
| 支持API | 完整Node.js | Web标准API |
| 包大小 | 无限制 | 有限制 |
| 数据库 | 支持所有 | 需要兼容 |
4.3 Edge示例 #
tsx
export const runtime = 'edge'
export async function GET(request: Request) {
const country = request.headers.get('x-vercel-ip-country') || 'Unknown'
return new Response(JSON.stringify({ country }), {
headers: { 'Content-Type': 'application/json' },
})
}
五、WebSocket #
5.1 升级请求 #
tsx
import { NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const upgradeHeader = request.headers.get('upgrade')
if (upgradeHeader !== 'websocket') {
return new Response('Expected WebSocket', { status: 426 })
}
return new Response('WebSocket support requires custom server', {
status: 501,
})
}
5.2 使用第三方服务 #
tsx
import { NextResponse } from 'next/server'
export async function GET() {
const wsUrl = process.env.WEBSOCKET_URL
return NextResponse.json({
websocket: wsUrl,
})
}
六、配置选项 #
6.1 动态配置 #
tsx
export const dynamic = 'force-dynamic'
export const dynamicParams = true
export const revalidate = 0
export const fetchCache = 'force-no-store'
export const runtime = 'nodejs'
6.2 路由段配置 #
| 配置 | 值 | 说明 |
|---|---|---|
| dynamic | ‘auto’ | 自动决定 |
| dynamic | ‘force-dynamic’ | 强制动态 |
| runtime | ‘nodejs’ | Node.js运行时 |
| runtime | ‘edge’ | Edge运行时 |
七、最佳实践 #
7.1 错误处理 #
tsx
import { NextResponse } from 'next/server'
export async function GET() {
try {
const data = await fetchData()
return NextResponse.json(data)
} catch (error) {
console.error('API Error:', error)
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
)
}
}
7.2 验证 #
tsx
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
export async function POST(request: NextRequest) {
const body = await request.json()
const result = schema.safeParse(body)
if (!result.success) {
return NextResponse.json(
{ error: 'Validation failed', details: result.error.issues },
{ status: 400 }
)
}
return NextResponse.json({ success: true })
}
7.3 日志 #
tsx
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const start = Date.now()
console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`)
const response = NextResponse.json({ data: 'test' })
console.log(`Completed in ${Date.now() - start}ms`)
return response
}
八、总结 #
Route Handlers要点:
| 要点 | 说明 |
|---|---|
| 流式响应 | ReadableStream |
| SSE | text/event-stream |
| 文件上传 | formData |
| Edge Runtime | runtime = ‘edge’ |
| 配置选项 | dynamic/runtime |
下一步,让我们学习Server Actions!
最后更新:2026-03-28