api
在 nextjs 中可以直接编写接口请求,提供类似后端的 api 功能,一般定义路由如下,在 api 文件夹中定义。
在 api 文件夹中定义 route.ts 文件,在该文件中定义接口路由,如下所示:
export async function GET(request: Request) {}
支持以下 HTTP 方法支持:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。如果调用不受支持的方法,Next.js 将返回响应 405 Method Not Allowed。
缓存
GET 当方法与对象一起使用时,默认情况下会缓存路由处理程序 Response。
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY
}
})
const data = await res.json()
return Response.json({ data })
}
清除缓存
- Request 将对象和方法一起使用 GET。
- 使用任何其他 HTTP 方法
- cookies 或 headers 包含特定值
- 配置时手动指定动态模式
export async function GET(request: Request) {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY
}
})
const data = await res.json()
return Response.json({ data })
}
使用 POST 来清除
export async function POST() {
const res = await fetch('https://data.mongodb-api.com/...', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY
},
body: JSON.stringify({ time: new Date().toISOString() })
})
const data = await res.json()
return Response.json(data)
}
route.js 和 page.js 不能位于同一个文件夹中,否则会出现错误。
页 | 路线 | 结果 |
---|---|---|
app/page.js | app/route.js | 冲突 |
app/page.js | app/api/route.js | 有效的 |
app/[user]/page.js | app/api/route.js | 有效的 |
例子
使用配置缓存验证
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
next: { revalidate: 60 } // Revalidate every 60 seconds
})
const data = await res.json()
return Response.json(data)
}
使用 cookies
import { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token.value}` }
})
}
也可以通过 web api 上读取 cookie
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
使用 headers 来读取请求头信息
import { headers } from 'next/headers'
export async function GET(request: Request) {
const headersList = headers()
const referer = headersList.get('referer')
return new Response('Hello, Next.js!', {
status: 200,
headers: { referer: referer }
})
}
也可以使用 next 提供的 api 来获取请求头信息
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
}
重定向
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
redirect('https://nextjs.org/')
}
获取动态路线段参数
export async function GET(request: Request, { params }: { params: { slug: string } }) {
const slug = params.slug // 'a', 'b', or 'c'
}
路线 | 示例网址 | 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' } |
获取 URL 查询参数
import { type NextRequest } from 'next/server'
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
// query is "hello" for /api/search?query=hello
}
流媒体获取
import { Configuration, OpenAIApi } from 'openai-edge'
import { OpenAIStream, StreamingTextResponse } from 'ai'
export const runtime = 'edge'
const apiConfig = new Configuration({
apiKey: process.env.OPENAI_API_KEY!
})
const openai = new OpenAIApi(apiConfig)
export async function POST(req: Request) {
// Extract the `messages` from the body of the request
const { messages } = await req.json()
// Request the OpenAI API for the response based on the prompt
const response = await openai.createChatCompletion({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages,
max_tokens: 500,
temperature: 0.7,
top_p: 1,
frequency_penalty: 1,
presence_penalty: 1
})
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response)
// Respond with the stream
return new StreamingTextResponse(stream)
}
也可以用底层 web api 获取
// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator: any) {
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next()
if (done) {
controller.close()
} else {
controller.enqueue(value)
}
}
})
}
function sleep(time: number) {
return new Promise((resolve) => {
setTimeout(resolve, time)
})
}
const encoder = new TextEncoder()
async function* makeIterator() {
yield encoder.encode('<p>One</p>')
await sleep(200)
yield encoder.encode('<p>Two</p>')
await sleep(200)
yield encoder.encode('<p>Three</p>')
}
export async function GET() {
const iterator = makeIterator()
const stream = iteratorToStream(iterator)
return new Response(stream)
}
获取请求正文
export async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
}
获取请求表单
export async function POST(request: Request) {
const formData = await request.formData()
const name = formData.get('name')
const email = formData.get('email')
return Response.json({ name, email })
}
设置跨域
export async function GET(request: Request) {
return new Response('Hello, Next.js!', {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
})
}
请求也可以返回非页面 ui 的内容
export async function GET() {
return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Next.js Documentation</title>
<link>https://nextjs.org/docs</link>
<description>The React Framework for the Web</description>
</channel>
</rss>`)
}
中间件
中间件和其他的中间件原理类似,允许您在请求完成之前运行代码。在项目根目录中的 middleware.ts 来定义中间件。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*'
}
匹配路径顺序
- headers 从 next.config.js
- redirects 从 next.config.js
- 中间件(rewrites、redirects 等)
- beforeFiles( rewrites) 来自 next.config.js
- 文件系统路由(public/、_next/static/、pages/、app/等)
- afterFiles( rewrites) 来自 next.config.js
- 动态路由 ( /blog/[slug])
- fallback( rewrites) 来自 next.config.js
匹配器
matcher 可以让中间件在特定的路径上运行。
export const config = {
matcher: '/about/:path*'
}
也可以匹配单个路径或者多个路径
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*']
}
还可以支持反向选择,排除部分路径
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)'
]
}
使用条件判断使用
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse api 的使用
- 可以 redirect 到不同的 url
- 可以 rewrite 到不同的 url
- 设置响应的 cookie
- 设置响应的 header
使用 Cookie
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Setting cookies on the response using the `ResponseCookies` API
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/'
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header.
return response
}
设置 header
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders
}
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
响应结果
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*'
}
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json({ success: false, message: 'authentication failed' }, { status: 401 })
}
}
中间件的高级处理
在 v13.1Next.js 中,为中间件引入了两个附加标志,skipMiddlewareUrlNormalize 并 skipTrailingSlashRedirect 处理高级用例。
skipTrailingSlashRedirect 允许禁用 Next.js 默认重定向以添加或删除尾部斜杠,允许中间件内部进行自定义处理,这可以允许维护某些路径的尾部斜杠,但不允许维护其他路径的尾部斜杠,从而允许更轻松的增量迁移。
module.exports = {
skipTrailingSlashRedirect: true
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}
skipMiddlewareUrlNormalize 允许禁用 URL 规范化 Next.js 的作用,以使直接访问和客户端转换的处理相同。在某些高级情况下,您需要使用解锁的原始 URL 进行完全控制。
module.exports = {
skipMiddlewareUrlNormalize: true
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
小结
通过 next 中编写 api 可以实现后端的接口请求,然后通过中间件,可以在请求的过程中进行一些额外的处理,来实现一些功能。