紀錄工作經驗、相關知識,解決技術相關問題。

Javascript, Next, React, 工作雜談, 新手 Jamp Holes, 網站相關, 資訊相關

Next.js Middleware:掌握請求攔截與修改

Next.js Middleware:掌握請求攔截與修改 特色圖片

Next.js Middleware(Middleware)是基於 Edge Network(邊緣網路)架構設計的請求攔截器,會在請求到達 Next.js 頁面渲染或 API 路由前先行執行。透過 Middleware,開發者可以在邊緣端以極低延遲完成身份驗證、路由重定向 (Redirect)、請求重寫 (Rewrite) 及 Header 標頭修改,是優化網站安全與存取效能的關鍵機制。

⚡ 快速跳轉目錄

Next 中的 Middleware 是什麼?

Middleware(Middleware)是一種在請求 (Request) 到達特定路由或 API 之前執行的函式。在 Next.js 中,Middleware 可用於攔截並修改請求,使開發者能夠靈活地進行身份驗證、請求重寫、日誌記錄等。

與傳統 Express.js 或 Koa.js 的 Middleware 不同,Next.js 的 Middleware 預設運行於 Vercel 的 Edge Runtime(基於標準 Web APIs),運行在 Edge Network(邊緣網路),因此具有極快的響應速度。

此外,Express.js 的 Middleware 通常運行於傳統 Node.js 伺服器(Server)內部,支援完整的 Node.js 原生 API 與 req.body 解析,而 Next.js Middleware 則更偏向於請求到達前的輕量化攔截與修改,適合應用於身份驗證、A/B 測試及 SEO 優化等場景。

Middleware 的運行時機與架構流向

Middleware 在請求剛進入 Next.js 應用之前執行,它通常在瀏覽器發送請求後、Next.js 處理路由與頁面渲染前運行,適用情境包含:

  • 用戶從瀏覽器地址列輸入網址並按 Enter
  • 點擊網頁上的連結以導向其他頁面
  • 透過 JavaScript 發送 AJAX / fetch 請求
  • 表單提交後觸發的 HTTP 請求 (Form Submit)

請求處理架構流向

[ 瀏覽器 HTTP 請求 ]
       │
       ▼
[ Edge Network: Middleware (攔截/驗證) ]
       │
       ├── (未授權/需跳轉) ──► [ 307 重定向 / Redirect ]
       │
       ▼ (通過/Rewrite)
[ Next.js 應用層 (Pages / App Router / API Routes) ]

Middleware 的基本使用與 TypeScript 範例

1. 建立 middleware.ts

在 Next.js 應用的 src 或根目錄下建立 middleware.ts,建議採用 TypeScript 搭配 NextRequest 與 NextResponse 型別標註:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 取得 Cookie 中的 Token 進行基本驗證
  const token = request.cookies.get('auth_token')?.value;

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

2. 設定 Middleware 作用範圍

如果 Middleware 只需要作用於特定路徑,可在 middleware.ts 中匯出 config 物件來設定匹配的路由。

Next.js 提供 matcher 屬性來定義 Middleware 的作用範圍,支援路徑模式匹配。例如:

export const config = {
  matcher: [
    '/dashboard/:path*',
    '/admin/:path*'
  ]
};

這代表 Middleware 只會作用於 /dashboard/ 和 /admin/ 相關頁面,而不影響其他公開請求。

Middleware 與 API Routes 的差異

要使用 Middleware 前,必須先了解它與 API Routes 的權責分工,兩者的差異如下表:

比較項目MiddlewareAPI Routes
運行位置邊緣網路 (Edge Network)應用伺服器端 (Node.js / Serverless)
適用場景攔截請求、重寫 URL、權限驗證、Header 調整處理商業邏輯、資料庫存取、檔案上傳
延遲時間極低(靠近使用者的 Edge 節點)視伺服器地理位置與效能而定
影響範圍由 matcher 定義的全域或區域請求僅限特定 API 路徑(如 /api/*)

舉例來說,假設我們有一個用於管理後台的 /admin 路由:

  • Middleware 處理身份驗證:攔截請求並檢查使用者是否持有有效的 JWT,若無則於邊緣端直接重定向至 /login。
  • API Routes 處理數據請求:在 /api/admin/users 內部連接資料庫(如 PostgreSQL 或 MongoDB)執行查詢並返回 JSON 結果。

這樣的組合能確保應用層級的安全性,同時保持數據存取的靈力與效能。

Middleware 的限制與注意事項

雖然 Middleware 執行效率極高,但因其運行環境為 Edge Runtime,在使用上需特別注意以下機制限制:

1. 不支援直接回傳 HTML 或完整 JSON 響應

Edge Middleware 的核心目標是「路由與標頭過濾」,而非取代後端渲染或 API 回應。Middleware 應透過 NextResponse.next()、NextResponse.redirect() 或 NextResponse.rewrite() 傳遞請求。若直接於 Middleware 內回傳自訂 JSON 物件,會引發型別錯誤。若需要處理 API 邏輯並回傳 JSON,應於 API Routes 中實作。

/*** 錯誤寫法 (Wrong Example) ***/
export function middleware(req: NextRequest) {
  // ❌ 嚴禁直接將物件作為 Response 回傳
  return { status: 401, message: 'Unauthorized' };
}

/*** 解法程式碼 (Correct Example) ***/
export function middleware(req: NextRequest) {
  if (!req.cookies.has('session')) {
    // ✅ 正確使用 NextResponse 回傳標準 Response 狀態
    return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
  }
  return NextResponse.next();
}

2. Edge Runtime 對 fetch() 的支援與存取邊界

過往常有「Middleware 不支援 fetch()」的誤解。事實上,Edge Runtime 原生完整支援標準 Web API fetch(),開發者可直接在 Middleware 中發送 HTTP 請求(例如向第三方 Token 驗證服務或 Auth0 / Supabase 發送請求)。

但使用 fetch() 時必須注意以下限制:

  • 執行逾時限制:Edge Function 的生命週期極短(通常為 1.5 秒至 30 秒內,依部署平台而定),遠端 API 若回應過慢會導致請求失敗。
  • 不支援 Node.js 原生模組:無法使用 Node.js 獨有的原生模組(如 fs、net、child_process),僅能呼叫符合 Web Standard API 的套件。
/*** 在 Middleware 中使用標準 fetch() 驗證 Token ***/
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  const token = request.headers.get('Authorization');

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // ✅ Edge Runtime 原生支援 fetch() API
  const authResponse = await fetch('https://api.example.com/v1/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token }),
  });

  if (!authResponse.ok) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

3. req.body 無法直接讀取與存取權限表

在 Middleware 中,無法直接讀取或解析 req.body(或 HTTP POST 的 Payload)。這是因為 HTTP 請求體屬於數據串流(Stream),若在邊緣端提前消耗(Consume)該串流,後續到達 API Route 或頁面時將無法再次讀取 Body。

以下整理 Middleware 中常見請求屬性的存取限制表:

屬性可存取可修改說明
req.url✅❌獲取請求的完整 URL 字串
req.nextUrl✅❌Next.js 解析後的 URL 物件,適用於 Rewrite 與 Redirect
req.method✅❌獲取 HTTP 請求方法(如 GET、POST)
req.headers✅✅可讀取與透過 NextResponse 設定請求與回應標頭
req.cookies✅✅可讀取與寫入 Cookie(如 req.cookies.get('token'))
req.ip✅❌取得客戶端 IP 地址
req.body❌❌無法讀取,強行解析將導致串流鎖定或錯誤,需於 API Routes 處理

常見問題(Q&A)

Q1:能在 Next.js Middleware 中連接資料庫(如 ORM / PostgreSQL)嗎?

不建議。大部分傳統資料庫 ORM(如舊版 Prisma 或 pg 驅動)依賴 Node.js 原生 TCP Socket 模組,無法直接在 Edge Runtime 執行。若需要存取資料庫,建議使用支援 HTTP / WebSockets 的邊緣資料庫 SDK(如 Supabase、Neon、PlanetScale),或將資料庫邏輯移至 API Routes 中。

Q2:為什麼在 Middleware 設定了 response.headers.set(),後端頁面卻讀取不到?

若要將修改後的 Header 傳遞給後續的頁面或 API 路由,必須將修改寫入 request.headers,並於 NextResponse.next({ request: { headers } }) 中帶入,而非僅修改回傳的 Response Header。

// ✅ 正確帶入修改後的 Request Headers
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-user-id', '12345');

return NextResponse.next({
  request: {
    headers: requestHeaders,
  },
});

Q3:Middleware 檔案可以放置在哪些目錄?

Next.js 規定 middleware.ts(或 .js)必須放置於專案的根目錄(與 package.json 同級)或 src/ 目錄下。每個 Next.js 專案僅能有一個主 Middleware 進入點檔案。

Q4:如何在開發環境確認 Middleware 是否正確執行?

可以在 Middleware 函式內部使用 console.log() 印出日誌。當請求觸發時,訊息會印出於啟動 next dev 的 Terminal 控制台中(而非瀏覽器的 Console)。此外,亦可透過瀏覽器 DevTools 的 Network 分頁檢查 Request / Response Header 變化進行排查。

結論

Middleware 在 Next.js 中提供了強大的請求攔截能力,結合 Edge Network 網路架構,能有效將授權檢查、語系切換與路由轉向等邏輯前移至邊緣端處理。只要掌握 Edge Runtime 的 API 支援邊界並避免在 Middleware 中處理串流 Body 或過重的同步運算,就能大幅減輕伺服器負載,打造具備高安全性與極速響應的現代 Web 應用。

參考資料與延伸閱讀

發表迴響