Route Handler

Route Handler 允许我们使用 request 和 response API 为给定的路由创建自定义的请求处理程序。

Route Handler 只能用于 app 文件夹,它与 pages 文件夹内的 API 路由是等价的。

约定

Route Handlers 在 APP 文件夹内的route.js|ts文件中定义:

ts
1//    -- 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 对象。

js
1//   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作为类型化的响应。

选择退出缓存

你可以通过以下的方式退出缓存:

  • 使用GET method 中的 Request 对象
  • 使用其他 HTTP Methods
  • 使用动态函数,诸如 cookies 和 headers
  • Segment 配置选项 手动指定动态模式。

举个例子:

ts
1//  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 方法将导致路由处理程序动态评估。

ts
1import { 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 文件。

PageRouteResult
app/page.jsapp/route.jsConflict
app/page.jsapp/api/route.jsValid
app/[user]/page.jsapp/api/route.jsValid

每个 route.js 或 page.js 文件会接管该路由的所有 HTTP 动词。

js
1export 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 选项验证缓存数据。

js
1import { 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:

json
1export const revalidate = 60

动态函数

Route Handlers 可以被用于动态函数,诸如 cookies 或者 headers。

Cookies

你可以从next/headers中读取 cookies,这个服务端函数可以在 Route Handler 中直接被调用,或者在其他方法中被嵌套使用。

cookie 实例是只读的,如果要设置 cookies,你需要使用 Set-Cookie 返回一个新的 Response header。

js
1import { 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):

js
1import { 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。

js
1import { 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。

js
1import { type NextRequest } from 'next/server'
2
3export async function GET(request: NextRequest) {
4  const requestHeaders = new Headers(request.headers)
5}

重定向

ts
1import { redirect } from 'next/navigation'
2
3export async function GET(request: Request) {
4  redirect('https://nextjs.org/')
5}

动态路由 Segments

Route Handlers 可以用动态 Segment 来基于动态的数据创建请求。

ts
1//   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}
RouteExample URLparams
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 的信息。

ts
1import { 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 直接创建流式传输的方式:

js
1// 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对象。

js
1import { 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:

js
1export 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 运行时

您可以使用运行时段配置选项来指定运行时:

js
1export const runtime = 'edge' // 'nodejs' is the default

非 UI 响应

您可以使用路由处理程序来返回非 UI 内容。

请注意,sitemap.xml、robots.txt、app icons和 open graph images 都有内置支持。

js
1export 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 配置选项

路由处理程序使用与页面和布局相同的路由段配置。

js
1export const dynamic = 'auto'
2export const dynamicParams = true
3export const revalidate = false
4export const fetchCache = 'auto'
5export const runtime = 'nodejs'
6export const preferredRegion = 'auto'