這幾天我們連續看了一些 Router 的用法,今天我們稍微偏離 Router 一下,從用 Router 設定權限控制的實作情境,延伸到另一個不同層面的權限控管方式,也就是 「透過 Middleware 來實作權限控管」的部分。除了看要怎麼透過 Middleware 控制權限外,也會來認識一下 Middleware。
那就直接入正題!
「Middleware」這個詞,從文字面來看意思是「中介軟體」或「中介層」,再進一步說明的話,指的是在兩個邏輯層或兩個模組之間,進行一些額外的邏輯處理。
Middleware 這個概念不只使用在 Next.js 這個框架中,也被應用於其他實務中,例如 API 系統、資料交換流程等。
在 Next.js 中,Middleware 是在伺服器接收到使用者請求之後,進入對應頁面或 API 處理之前所執行的中介邏輯。換句話說,它是在頁面還沒被真正渲染前,就先對請求進行攔截與處理的機制。因此,Middleware 常被用來執行重新導向(Redirect)、修改 request 資料(如 URL 重寫、加上 headers) 等操作。
大概了解 Middlearware 以及 Middleware 在 Next.js 中是什麼樣的角色後,也來進一步看一下在 Page Router 上,以及 App Router 上的 Middleware 有什麼差異?
關於使用方法的部分,在 Page Router 和 App Router 上並沒有差異。就如同前面所提到過的內容,位於 Middleware 的邏輯會在伺服器接受使用者 request 後,進入對應頁面前執行,所以會是在 Next.js 伺服器層進化的功能,也就是說在還沒進入 Router 配置內容前就會執行 Middleware。
不論是 Page Router 還是 App Router 都是透過在專案內的 src 資料夾底下建立名為 middleware
的檔案來加入 Middleware 的內容。
middleware 檔案內的內容大概會是以下這個樣子。
import { NextResponse, NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
export const config = {
matcher: '/about',
}
雖然在 Next.js 的 Page Router 或是 App Router 中可以在 Server 端上在進入頁面之前,就做權限的判斷決定要不要繼續進入該頁面,但如果想要在發送 Request 後,進入 Router 設定之前,就做權限的判斷,則可以考慮改成透過 Middleware 來處理。
緊接著我們就直接來看看要怎麼透過 Middleware 來處理頁面的權限控管吧!
不論是在 Page Router 或是 App Router 都可以透過以下的這個寫法去做頁面權限控管的處理。
就如同前面所說的一樣,先建立一個 middleware 檔案在 src 的資料夾內。
然後在於 middleware 檔案內透過 config 來定義 matcher,以及透過 NextResponse 來處理當對應到特定網址後,進一步依照有沒有 token 後,做相對應的處理。
import { NextResponse, NextRequest } from "next/server";
// 定義哪些路徑要被 Middleware 處理
export const config = {
matcher: ['/about'], // about 頁面要被 middleware 處理
};
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 有 token 就繼續往下進行
return NextResponse.next();
}
這樣寫的話,即使 Router 中沒有做任何權限判斷的處理,也能處理可不可以進入頁面的權限處理。
我們已經知道不論 Page Router 還是 App Router 都可以透過 Middleware 來處理頁面權限控管,那我們再進一步思考一下在 App Router 中用 Next.js 提供的 layout 功能就能很方便地處理權限控制了,為什麼還需要使用 Middleware 呢?它們之間的差異是什麼?
在正式談兩者的差異之前,先來回顧一下在 App Router 中透過 layout 去做頁面權限的處理,和使用 Middleware 處理頁面權限處理時,實際上經歷了哪些流程。
- App Router 的 layout
使用者發送 HTTP Request → Next.js 根據 request 的 URL 找到 Router 設定內容對應的路由片段 (Route Segment) → 執行此路由片段 (Route Segment) 的 layout 檔案 → 透過 layout 檔案判斷是否有權限 → 有權限:渲染對應的頁面;無權限:導轉到特定頁面
- Middleware
使用者發送 HTTP Request → 進入 middlware 檔案 → 在 middleware 檔案內根據 URL 及 cookies 斷進入頁面的權限 → 有權限:呼叫 NextResponse.next() 繼續進入對應的 Route;無權限:NextResponse.redirect() 導向指定頁面 → 根據要進入到哪個頁面的結果,找到對應的路由片段 (Route Segment) → 執入對應路由片段 (Route Segment) 的 layout 檔案 → 渲染對應的頁面
重新回顧上述兩者渲染出頁面的過程,其實就可以感受到雖然達到的目的相同,但卻有差異存在,兩者之間的差異主要就是在於做權限判斷的時機點不同
。
使用 App Router 的 layout 進行頁面的權限判斷時,會先經過 Router 設定、匹配到對應的路由片段 (Route Segment),才會進入到透過 layout 的邏輯進行權限控制的部分。因此即使最後被導向到其他頁面,還是會先觸發一部分的頁面初始化(如果在某些 layout 內容較複雜的情況下,可能會影響效能);而透過 Middleware 進行權限判斷,則是會在還沒匹配到對應的路由片段 (Route Segment) 之前就進行判斷,也就可以避免掉不必要的頁面初始化及渲染。
雖然從流程來看,透過 Middleware 來處理權限控管似乎比透過 layout 還更合適且更有效率,因為它可以在進入頁面之前就完成驗證,避免不必要的渲染與資源浪費。但是在 Middleware 階段做處理的話,因為沒辦法接收 props,所以如果判斷邏輯很複雜,或是需要一些動態資料進行判斷,並不是只使用 token 下去做判斷,就不適合使用 Middleware 來處理。
今天 Middleware 的主題就到這裡告一個段落了!這裡必須要補充一個部分,雖然我們前面提到的都是用 Middleware 來處理頁面的權限控制,但是 Middleware 除了可以用來做頁面的權限控管外,還可以應用於各種「請求到達伺服器後、進入頁面前」的需求。像是 URL 的 Rewrite 或 Redirect、在 request 上加上特定的 Header 或是 Cookie 等。
總結來說,Middleware 適合用於「不依賴 React、只靠請求資訊就能判斷的邏輯」,如果是和畫面渲染,甚至是和元件狀態有關的邏輯,就不適合使用 Middleware 來處理。