Route Handler
Route Handler 允许我们使用 request 和 response API 为给定的路由创建自定义的请求处理程序。
Route Handler 只能用于 app 文件夹,它与 pages 文件夹内的 API 路由是等价的。
约定
Route Handlers 在 APP 文件夹内的route.js|ts文件中定义:
ts1// -- app/api/route.ts 2export async function GET(request: Request) {}
它可以在 app 文件夹中被嵌套,类似于 page.js或者layout.js。但是不能有一个 route.js 文件与 page.js 处于相同的路由 segment 级别。
支持的 HTTP Methods
以下的 HTTP Methods 是支持的:GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS。
如果有一个不被支持的 Methods 被调用,会返回405 Method Not Allowed。
扩展的NextRequest and NextResponse APIs
除了支持原生的 Request 和 Response 外,Nextjs 扩展了出NextRequest和NextResponse为高级的用例提供方便的帮助。
性能
缓存
Route Handlers 默认情况下会缓存 GET请求的 Response 对象。
js1// app/items/route.ts 2import { NextResponse } from 'next/server' 3 4export async function GET() { 5 const res = await fetch('https://data.mongodb-api.com/...', { 6 headers: { 7 'Content-Type': 'application/json', 8 'API-Key': process.env.DATA_API_KEY, 9 }, 10 }) 11 const data = await res.json() 12 13 return NextResponse.json({ data }) 14}
虽然
Response.json()也是有效的,但是原生的 Typescript 目前会报错,所以可以使用NextResponse.json作为类型化的响应。
选择退出缓存
你可以通过以下的方式退出缓存:
- 使用
GETmethod 中的 Request 对象 - 使用其他 HTTP Methods
- 使用动态函数,诸如 cookies 和 headers
- Segment 配置选项 手动指定动态模式。
举个例子:
ts1// app/products/api/route.ts 2import { NextResponse } from 'next/server' 3 4export async function GET(request: Request) { 5 const { searchParams } = new URL(request.url) 6 const id = searchParams.get('id') 7 const res = await fetch(`https://data.mongodb-api.com/product/${id}`, { 8 headers: { 9 'Content-Type': 'application/json', 10 'API-Key': process.env.DATA_API_KEY, 11 }, 12 }) 13 const product = await res.json() 14 15 return NextResponse.json({ product }) 16}
类似地,使用 POST 方法将导致路由处理程序动态评估。
ts1import { NextResponse } from 'next/server' 2 3export async function POST() { 4 const res = await fetch('https://data.mongodb-api.com/...', { 5 method: 'POST', 6 headers: { 7 'Content-Type': 'application/json', 8 'API-Key': process.env.DATA_API_KEY, 9 }, 10 body: JSON.stringify({ time: new Date().toISOString() }), 11 }) 12 13 const data = await res.json() 14 15 return NextResponse.json(data) 16}
与 API 路由类似,路由处理程序可以用于处理表单提交等情况。目前正在开发一个与 React 深度集成的处理表单和变化的新抽象方法。
Route Resolution
你可以将路由视为最低级别的路由基元。
它们不参与 layout 或客户端导航,比如页面(page)。 在相同的路由下不能同时存在一个 route.js 文件和一个 page.js 文件。
| Page | Route | Result |
|---|---|---|
app/page.js | app/route.js | Conflict |
app/page.js | app/api/route.js | Valid |
app/[user]/page.js | app/api/route.js | Valid |
每个 route.js 或 page.js 文件会接管该路由的所有 HTTP 动词。
js1export default function Page() { 2 return <h1>Hello, Next.js!</h1> 3} 4 5// ❌ Conflict 6// `app/route.js` 7export async function POST(request) {}
例子
下面的例子将展示如何结合 Route Handlers 完成 Next.js 的 API 和其他功能。
重新验证缓存数据
通过 next.revalidate 选项验证缓存数据。
js1import { NextResponse } from 'next/server' 2 3export async function GET() { 4 const res = await fetch('https://data.mongodb-api.com/...', { 5 next: { revalidate: 60 }, // Revalidate every 60 seconds 6 }) 7 const data = await res.json() 8 9 return NextResponse.json(data) 10}
或者,你可以使用revalidate segment config option:
json1export const revalidate = 60
动态函数
Route Handlers 可以被用于动态函数,诸如 cookies 或者 headers。
Cookies
你可以从next/headers中读取 cookies,这个服务端函数可以在 Route Handler 中直接被调用,或者在其他方法中被嵌套使用。
cookie 实例是只读的,如果要设置 cookies,你需要使用 Set-Cookie 返回一个新的 Response header。
js1import { cookies } from 'next/headers' 2 3export async function GET(request: Request) { 4 const cookieStore = cookies() 5 const token = cookieStore.get('token') 6 7 return new Response('Hello, Next.js!', { 8 status: 200, 9 headers: { 'Set-Cookie': `token=${token.value}` }, 10 }) 11}
或者,您可以在底层 Web API 之上使用 request 来读取 cookie(NextRequest):
js1import { type NextRequest } from 'next/server' 2 3export async function GET(request: NextRequest) { 4 const token = request.cookies.get('token') 5}
Headers
你可以通过 next/headers读取 headers。
同 cookies 一样,拿到的 headers 实例是只读的。如果想要设置 headers,就需要返回一个带有新的 headers 的 Response。
js1import { headers } from 'next/headers' 2 3export async function GET(request: Request) { 4 const headersList = headers() 5 const referer = headersList.get('referer') 6 7 return new Response('Hello, Next.js!', { 8 status: 200, 9 headers: { referer: referer }, 10 }) 11}
还可以在 Request 中读取 headers。
js1import { type NextRequest } from 'next/server' 2 3export async function GET(request: NextRequest) { 4 const requestHeaders = new Headers(request.headers) 5}
重定向
ts1import { redirect } from 'next/navigation' 2 3export async function GET(request: Request) { 4 redirect('https://nextjs.org/') 5}
动态路由 Segments
Route Handlers 可以用动态 Segment 来基于动态的数据创建请求。
ts1// app/items/[slug]/route.ts 2export async function GET(request: Request, { params }: { params: { slug: string } }) { 3 const slug = params.slug // 'a', 'b', or 'c' 4}
| Route | Example URL | params |
|---|---|---|
app/items/[slug]/route.js | /items/a | { slug: 'a' } |
app/items/[slug]/route.js | /items/b | { slug: 'b' } |
app/items/[slug]/route.js | /items/c | { slug: 'c' } |
流
"流式传输"通常与大型语言模型(LLMs)如 OpenAI 一起使用,用于生成人工智能内容。了解更多关于 AI SDK 的信息。
ts1import { Configuration, OpenAIApi } from 'openai-edge' 2import { OpenAIStream, StreamingTextResponse } from 'ai' 3 4export const runtime = 'edge' 5 6const apiConfig = new Configuration({ 7 apiKey: process.env.OPENAI_API_KEY!, 8}) 9 10const openai = new OpenAIApi(apiConfig) 11 12export async function POST(req: Request) { 13 // Extract the `messages` from the body of the request 14 const { messages } = await req.json() 15 16 // Request the OpenAI API for the response based on the prompt 17 const response = await openai.createChatCompletion({ 18 model: 'gpt-3.5-turbo', 19 stream: true, 20 messages: messages, 21 max_tokens: 500, 22 temperature: 0.7, 23 top_p: 1, 24 frequency_penalty: 1, 25 presence_penalty: 1, 26 }) 27 28 // Convert the response into a friendly text-stream 29 const stream = OpenAIStream(response) 30 31 // Respond with the stream 32 return new StreamingTextResponse(stream) 33}
还有一种使用底层 Web API 直接创建流式传输的方式:
js1// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream 2function iteratorToStream(iterator: any) { 3 return new ReadableStream({ 4 async pull(controller) { 5 const { value, done } = await iterator.next() 6 7 if (done) { 8 controller.close() 9 } else { 10 controller.enqueue(value) 11 } 12 }, 13 }) 14} 15 16function sleep(time: number) { 17 return new Promise((resolve) => { 18 setTimeout(resolve, time) 19 }) 20} 21 22const encoder = new TextEncoder() 23 24async function* makeIterator() { 25 yield encoder.encode('<p>One</p>') 26 await sleep(200) 27 yield encoder.encode('<p>Two</p>') 28 await sleep(200) 29 yield encoder.encode('<p>Three</p>') 30} 31 32export async function GET() { 33 const iterator = makeIterator() 34 const stream = iteratorToStream(iterator) 35 36 return new Response(stream) 37}
Request Body
你可以通过request.formData函数读取FormData对象。
js1import { NextResponse } from 'next/server' 2 3export async function POST(request: Request) { 4 const formData = await request.formData() 5 const name = formData.get('name') 6 const email = formData.get('email') 7 return NextResponse.json({ name, email }) 8}
由于 formData 都是字符串,你可能需要用zod-form-data去验证请求以及处理数据。
CORS
你可以在 Response 中使用标准的 Web API 设置 CORS headers:
js1export async function GET(request: Request) { 2 return new Response('Hello, Next.js!', { 3 status: 200, 4 headers: { 5 'Access-Control-Allow-Origin': '*', 6 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 7 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 8 }, 9 }) 10}
Edge 和 Node.js 运行时
您可以使用运行时段配置选项来指定运行时:
js1export const runtime = 'edge' // 'nodejs' is the default
非 UI 响应
您可以使用路由处理程序来返回非 UI 内容。
请注意,sitemap.xml、robots.txt、app icons和 open graph images 都有内置支持。
js1export async function GET() { 2 return new Response(`<?xml version="1.0" encoding="UTF-8" ?> 3<rss version="2.0"> 4 5<channel> 6 <title>Next.js Documentation</title> 7 <link>https://nextjs.org/docs</link> 8 <description>The React Framework for the Web</description> 9</channel> 10 11</rss>`) 12}
segment 配置选项
路由处理程序使用与页面和布局相同的路由段配置。
js1export const dynamic = 'auto' 2export const dynamicParams = true 3export const revalidate = false 4export const fetchCache = 'auto' 5export const runtime = 'nodejs' 6export const preferredRegion = 'auto'