今天分享一个来自社区的 nextjs
非常好的中间件实践, 我们知道 nextjs
原生提供的中间件比较原始简陋,如果想实现比较多的功能代码实现起来会比较丑陋
这个版本主要是让 nextj
支持类似 express 中间件的模型,可以使用链式调用中间件
我们在
src
或者app
目录下新建middlewares
文件夹,在middlewares
文件夹下新建util
文件夹util
文件夹下新建chain.ts
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server' import { mergeHeaders } from './merge-headers' type GoNextMiddleware = () => 'continue' export type MiddlewareFunction = ( request: NextRequest, next: GoNextMiddleware, event: NextFetchEvent ) => Promise<NextResponse<unknown> | ReturnType<GoNextMiddleware>> export function composeMiddleware(handlers: MiddlewareFunction[] = []) { const validMiddlewareHandlers = handlers.filter((handler) => typeof handler === 'function') return async function (request: NextRequest, event: NextFetchEvent) { const allResponses: NextResponse[] = [] // 1. // run every middleware and collect responses (NextResponse) // until a middleware want to break the chain (redirect or rewrite) for (const fn of validMiddlewareHandlers) { const result = await fn(request, () => 'continue', event) // ensure that fn returned something or notify the dev if (result !== 'continue' && !(result instanceof NextResponse)) { console.error( `The middleware chain has been broken because '${fn.name}' did not return a NextResponse or call next().` ) return NextResponse.next() } // go next middleware if (result === 'continue') continue // we have a response allResponses.push(result) // the "middleware" function cannot return a native Response // @see https://nextjs.org/docs/messages/middleware-upgrade-guide#no-response-body // It can only : // - return `NextResponse.redirect() or NextResponse.rewrite()` // => this must break the chian // - return a mutated request using `NextResponse.next({ request: { /* ... */ }})` // => this must NOT break the chain const isRedirect = () => result.headers.get('Location') const isRewrite = () => result.headers.get('x-middleware-rewrite') if (isRedirect() || isRewrite()) { // break the chain break } } // 2. // return final response // middlewares have not returned any response, do nothing... if (allResponses.length === 0) return NextResponse.next() // only one middleware returned a response, return it if (allResponses.length === 1) return allResponses[0] // more than one middleware returned a response // merge headers into a final response and return it const finalResponse = allResponses[allResponses.length - 1] const finalHeaders: Headers = mergeHeaders(...allResponses.map((r) => r.headers)) for (const [key] of Array.from(finalResponse.headers.entries())) { finalResponse.headers.delete(key) } for (const [key, value] of Array.from(finalHeaders.entries())) { finalResponse.headers.set(key, value) } return finalResponse } }
utils
文件夹下新建merge-headers.ts
// @credits // https://github.com/whitecrownclown/merge-headers/blob/master/index.ts function isObject(value: any) { return value !== null && typeof value === 'object' } export function mergeHeaders(...sources: HeadersInit[]) { const result: Record<string, string> = {} for (const source of sources) { if (!isObject(source)) { throw new TypeError('All arguments must be of type object') } const headers: Headers = new Headers(source) for (const [key, value] of Array.from(headers.entries())) { if (value === undefined || value === 'undefined') { delete result[key] } else { result[key] = value } } } return new Headers(result) }
下边我们举个
proxy
中间件的例子说明如何使用这一套链式中间件,在middlewares
文件夹下新建 proxy.middleware.ts
import { NextResponse, type NextRequest } from 'next/server' import { MiddlewareFunction } from '@/middlewares/utils/chain' export const proxyMiddleware = (req: NextRequest) => { const destination = new URL(`${process.env.YOUR_BACKEND_SERVER}`) const url = req.nextUrl.clone() url.host = destination.host url.protocol = destination.protocol url.port = destination.port return NextResponse.rewrite(url) } export const handleProxyMiddleware: MiddlewareFunction = async (req, next) => { if (req.nextUrl.pathname.startsWith(`/api`)) { return proxyMiddleware(req) } return next() }
这个是一个生产可用版本的 proxy
middleware,用来代理访问后端接口,将 process.env.YOUR_BACKEND_SERVER
换成你自己的后端环境变量就OK了,我们可以看到这个 handleProxyMiddleware
包括了两个参数,一个是 next 包装后的 request
对象,另一个是 next() 方法,用来在执行结束后调用, 调用 next()
方法就会去到下一个中间件
接下来,在
middlewares
文件夹下新建index.ts
文件夹,我们把这个proxyMiddleware
放到这里来
import { handleProxyMiddleware } from './proxy.middleware' export const middlewares = [handleProxyMiddleware]
OK,这时候我们主要的部分就结束了,你可以把新建的其余的middlewares加入到这个 middlewares
数组里面,需要注意的是,这里的数组顺序就是中间件执行的顺序,如果你需要有序的中间件,这里要注意放置的顺序
最后一步了,在
nextjs
约定的src
或者app
或者pages
文件夹下新建middleware.ts
文件放入如下代码:
import { composeMiddleware } from '@/middlewares/utils/chain' import { middlewares } from './middlewares' export default composeMiddleware(middlewares) export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)', '/api/:path*'], }
到此为止,一个生产可用的,优雅的 nextjs
中间件就实现了,可以让你的代码更好的维护,分离中间件的各个职责,祝大家用起来开心 ^_^
作者:rickyshin93
链接:https://juejin.cn/post/7433615587262529573