今天分享一個來自社羣的 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