Next.js
路由处理器
深入理解 Next.js 路由处理器,包括 HTTP 方法、请求响应处理、流式传输、Cookie、Headers 和动态路由
概述
Route Handlers 允许你使用 Web Request 和 Response API 为给定路由创建自定义请求处理器。
Route Handlers 仅在 app 目录中可用。它们相当于 pages 目录中的 API Routes,这意味着你不需要同时使用 API Routes 和 Route Handlers。
约定
Route Handlers 在 app 目录内的 route.js|ts 文件中定义:
export async function GET(request: Request) {
return Response.json({ message: 'Hello World' })
}Route Handlers 可以嵌套在 app 目录内的任何位置,类似于 page.js 和 layout.js。但在与 page.js 相同的路由段级别不能有 route.js 文件。
支持的 HTTP 方法
支持以下 HTTP 方法:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。
export async function GET(request: Request) {
return Response.json({ message: 'GET request' })
}
export async function POST(request: Request) {
return Response.json({ message: 'POST request' })
}
export async function PUT(request: Request) {
return Response.json({ message: 'PUT request' })
}
export async function PATCH(request: Request) {
return Response.json({ message: 'PATCH request' })
}
export async function DELETE(request: Request) {
return Response.json({ message: 'DELETE request' })
}
export async function HEAD(request: Request) {
return new Response(null)
}
export async function OPTIONS(request: Request) {
return new Response(null, {
headers: {
Allow: 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS',
},
})
}如果调用不支持的方法,Next.js 将返回 405 Method Not Allowed 响应。
NextRequest 和 NextResponse
Next.js 扩展了原生 Request 和 Response API,提供了 NextRequest 和 NextResponse,为高级用例提供便捷的辅助函数。
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
return NextResponse.json({ query })
}请求处理
读取请求体
export async function POST(request: Request) {
const body = await request.json()
return Response.json({
message: 'User created',
user: body,
})
}读取查询参数
import { NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
const page = searchParams.get('page') || '1'
return Response.json({ query, page })
}读取 Headers
export async function GET(request: Request) {
const headersList = request.headers
const userAgent = headersList.get('user-agent')
const authorization = headersList.get('authorization')
return Response.json({ userAgent, authorization })
}读取 Cookies
import { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = await cookies()
const token = cookieStore.get('token')
return Response.json({ token: token?.value })
}响应处理
返回 JSON
export async function GET() {
return Response.json({ message: 'Hello World' })
}返回文本
export async function GET() {
return new Response('Hello World', {
headers: {
'Content-Type': 'text/plain',
},
})
}返回 HTML
export async function GET() {
return new Response('<h1>Hello World</h1>', {
headers: {
'Content-Type': 'text/html',
},
})
}设置状态码
export async function POST(request: Request) {
const body = await request.json()
return Response.json(
{ message: 'Created successfully' },
{ status: 201 }
)
}设置 Headers
export async function GET() {
return new Response('Hello World', {
headers: {
'Content-Type': 'text/plain',
'X-Custom-Header': 'custom-value',
},
})
}设置 Cookies
import { NextResponse } from 'next/server'
export async function GET() {
const response = NextResponse.json({ message: 'Hello World' })
response.cookies.set('token', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // 1 week
})
return response
}删除 Cookies
import { NextResponse } from 'next/server'
export async function GET() {
const response = NextResponse.json({ message: 'Logged out' })
response.cookies.delete('token')
return response
}重定向
import { redirect } from 'next/navigation'
export async function GET() {
redirect('https://nextjs.org/')
}使用 NextResponse:
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.redirect(new URL('/new-path', request.url))
}动态路由
路由参数
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
return Response.json({ postId: id })
}多个路由参数
export async function GET(
request: Request,
{ params }: { params: Promise<{ category: string; id: string }> }
) {
const { category, id } = await params
return Response.json({ category, postId: id })
}Catch-all 路由
export async function GET(
request: Request,
{ params }: { params: Promise<{ path: string[] }> }
) {
const { path } = await params
return Response.json({ path })
}流式传输
流式响应
export async function GET() {
const encoder = new TextEncoder()
const customReadable = new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode('Hello '))
await new Promise((resolve) => setTimeout(resolve, 1000))
controller.enqueue(encoder.encode('World'))
controller.close()
},
})
return new Response(customReadable, {
headers: {
'Content-Type': 'text/plain',
'Transfer-Encoding': 'chunked',
},
})
}使用 AI SDK 流式传输
import { openai } from '@ai-sdk/openai'
import { streamText } from 'ai'
export async function POST(request: Request) {
const { messages } = await request.json()
const result = streamText({
model: openai('gpt-4-turbo'),
messages,
})
return result.toDataStreamResponse()
}CORS
设置 CORS Headers
export async function GET(request: Request) {
return new Response('Hello World', {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}处理 OPTIONS 请求
export async function OPTIONS(request: Request) {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}非 UI 响应
返回 sitemap.xml
export async function GET() {
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com</loc>
<lastmod>2023-04-06</lastmod>
</url>
</urlset>`,
{
headers: {
'Content-Type': 'text/xml',
},
}
)
}返回 robots.txt
export async function GET() {
return new Response(
`User-agent: *
Allow: /
Disallow: /private/
Sitemap: https://example.com/sitemap.xml`,
{
headers: {
'Content-Type': 'text/plain',
},
}
)
}返回 RSS Feed
export async function GET() {
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>My Blog</title>
<link>https://example.com</link>
<description>My blog description</description>
</channel>
</rss>`,
{
headers: {
'Content-Type': 'text/xml',
},
}
)
}段配置选项
Route Handlers 使用与 pages 和 layouts 相同的路由段配置。
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
export const maxDuration = 5
export async function GET() {
return Response.json({ message: 'Hello World' })
}dynamic
控制路由的动态行为:
export const dynamic = 'force-dynamic' // 'auto' | 'force-dynamic' | 'error' | 'force-static'
export async function GET() {
return Response.json({ timestamp: Date.now() })
}revalidate
设置路由的重新验证时间(秒):
export const revalidate = 60 // 每 60 秒重新验证
export async function GET() {
const data = await fetch('https://api.example.com/data')
return Response.json(data)
}runtime
指定运行时环境:
export const runtime = 'edge' // 'nodejs' | 'edge'
export async function GET() {
return Response.json({ message: 'Running on Edge' })
}错误处理
基本错误处理
export async function POST(request: Request) {
try {
const body = await request.json()
if (!body.email) {
return Response.json(
{ error: 'Email is required' },
{ status: 400 }
)
}
// 创建用户逻辑
return Response.json({ message: 'User created' }, { status: 201 })
} catch (error) {
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}自定义错误响应
class APIError extends Error {
constructor(public statusCode: number, message: string) {
super(message)
}
}
export async function GET() {
try {
throw new APIError(404, 'Resource not found')
} catch (error) {
if (error instanceof APIError) {
return Response.json(
{ error: error.message },
{ status: error.statusCode }
)
}
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}身份验证
验证 Token
export async function GET(request: Request) {
const token = request.headers.get('authorization')?.split(' ')[1]
if (!token) {
return Response.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
try {
// 验证 token
const user = await verifyToken(token)
return Response.json({ user })
} catch (error) {
return Response.json(
{ error: 'Invalid token' },
{ status: 401 }
)
}
}使用 Cookies 进行身份验证
import { cookies } from 'next/headers'
export async function GET() {
const cookieStore = await cookies()
const sessionToken = cookieStore.get('session')
if (!sessionToken) {
return Response.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
const user = await getUserFromSession(sessionToken.value)
return Response.json({ user })
}文件上传
处理文件上传
import { writeFile } from 'fs/promises'
import { NextRequest } from 'next/server'
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 Response.json(
{ error: 'No file uploaded' },
{ status: 400 }
)
}
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const filePath = path.join(process.cwd(), 'public', 'uploads', file.name)
await writeFile(filePath, buffer)
return Response.json({
message: 'File uploaded successfully',
filename: file.name
})
}代理请求
转发请求到外部 API
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const endpoint = searchParams.get('endpoint')
if (!endpoint) {
return Response.json(
{ error: 'Endpoint is required' },
{ status: 400 }
)
}
const response = await fetch(`https://api.example.com/${endpoint}`, {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`,
},
})
const data = await response.json()
return Response.json(data)
}最佳实践
1. 使用 TypeScript 类型
import { NextRequest, NextResponse } from 'next/server'
type User = {
id: string
name: string
email: string
}
export async function GET(request: NextRequest): Promise<NextResponse<User[]>> {
const users: User[] = await getUsers()
return NextResponse.json(users)
}2. 验证输入数据
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
export async function POST(request: Request) {
try {
const body = await request.json()
const validatedData = userSchema.parse(body)
// 使用验证后的数据
return Response.json({ user: validatedData }, { status: 201 })
} catch (error) {
if (error instanceof z.ZodError) {
return Response.json(
{ error: error.errors },
{ status: 400 }
)
}
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}3. 使用环境变量
export async function GET() {
const apiKey = process.env.API_KEY
if (!apiKey) {
return Response.json(
{ error: 'API key not configured' },
{ status: 500 }
)
}
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
})
const data = await response.json()
return Response.json(data)
}4. 实现速率限制
const rateLimit = new Map<string, number[]>()
function checkRateLimit(ip: string): boolean {
const now = Date.now()
const windowMs = 60 * 1000 // 1 minute
const maxRequests = 10
const requests = rateLimit.get(ip) || []
const recentRequests = requests.filter((time) => now - time < windowMs)
if (recentRequests.length >= maxRequests) {
return false
}
recentRequests.push(now)
rateLimit.set(ip, recentRequests)
return true
}
export async function GET(request: Request) {
const ip = request.headers.get('x-forwarded-for') || 'unknown'
if (!checkRateLimit(ip)) {
return Response.json(
{ error: 'Too many requests' },
{ status: 429 }
)
}
return Response.json({ message: 'Success' })
}5. 使用缓存
export const revalidate = 3600 // 缓存 1 小时
export async function GET() {
const data = await fetch('https://api.example.com/data')
return Response.json(data)
}6. 处理 CORS
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: corsHeaders,
})
}
export async function GET() {
return Response.json(
{ message: 'Hello World' },
{ headers: corsHeaders }
)
}总结
Next.js Route Handlers 提供了强大的 API 路由功能:
- HTTP 方法:支持所有标准 HTTP 方法
- 请求处理:读取请求体、查询参数、Headers 和 Cookies
- 响应处理:返回 JSON、文本、HTML,设置状态码和 Headers
- 动态路由:支持路由参数和 catch-all 路由
- 流式传输:支持流式响应
- CORS:轻松配置跨域资源共享
- 段配置:控制缓存、运行时和重新验证行为
- 错误处理:完善的错误处理机制
- 身份验证:支持 Token 和 Cookie 身份验证
- 文件上传:处理文件上传
通过合理使用 Route Handlers,可以构建强大、安全、高性能的 API 端点。
在 GitHub 上编辑
上次更新于