Skip to main content

從 Next.js 的 Proxy 跟 Middleware 來理解請求的生命週期

3 min read
Robot directing data traffic at a futuristic checkpoint, illustrating Next.js Proxy and Rewrites flow

前陣子在升級 Next.js 16 的時候踩到一些坑,發現 middleware.ts 被改名成 proxy.ts 了,然後 Edge Runtime 也不支援了,搞得我一臉問號。查了一下資料順便把 Rewrites 也一起整理了,想說既然都整理了,不如就寫篇文章好了。

先聲明一下,雖然標題寫「請求的生命週期」,但其實沒有講得很底層,比較像是從實務角度去理解 Next.js 處理請求的幾個機制。只是我沒想到比這個更好的標題 ╮(╯_╰)╭


1. 先釐清名詞

在 Next.js 裡面有三個東西很容易搞混:

  • Rewrites:在 next.config.ts 裡面設定的路由轉發規則
  • Middleware:放在專案根目錄的 middleware.ts,每個 request 都會經過的程式碼(Next.js 15 以前)
  • Proxy:Next.js 16 把 Middleware 改名了,檔案從 middleware.ts 變成 proxy.ts

簡單說 Rewrites 是設定檔層級的東西,Middleware/Proxy 是程式碼層級的東西,它們做的事不太一樣。


2. Rewrites:設定檔裡的路由轉發

Rewrites 的概念很直覺,就是「把 A 路徑偷偷導到 B 位址」。最常見的場景就是前端要串後端 API 的時候:

import { NextConfig } from 'next' const nextConfig: NextConfig = { async rewrites() { return [ { source: '/api/:path*', destination: 'https://your-backend.com/:path*', }, ] }, } export default nextConfig

前端打 /api/users,Next.js 幫你轉到 https://your-backend.com/users,瀏覽器完全不知道這件事,所以也不會有 CORS 的問題。

條件式的 Rewrites

比較少人知道的是,Rewrites 可以根據 Header、Cookie、Query 來決定要不要觸發:

module.exports = { async rewrites() { return [ { source: '/:path*', has: [ { type: 'header', key: 'x-rewrite-me', }, ], destination: '/another-page', }, ] }, }

看起來好像很彈性?但其實能做的事還是有限,就是比對條件然後決定要不要轉發,沒辦法執行任何邏輯。

Fallback 模式

Rewrites 還有一個 fallback 模式,在漸進式遷移的時候特別好用:

module.exports = { async rewrites() { return { fallback: [ { source: '/:path*', destination: 'https://old-website.com/:path*', }, ], } }, }

所有 Next.js 的路由都比對完了還是沒找到的話,就丟回舊網站。這樣可以一頁一頁慢慢搬,不用一次全部重寫,誰受得了一次全部重寫 (´;ω;`)


3. Middleware:請求進來的第一道關卡

Middleware 就不一樣了,它是一段真正會執行的程式碼。放在專案根目錄的 middleware.ts,每個符合條件的 request 都會先經過它。

最經典的用法就是 Auth 驗證:

import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { decrypt } from '@/app/lib/session' import { cookies } from 'next/headers' const protectedRoutes = ['/dashboard'] const publicRoutes = ['/login', '/signup', '/'] export default async function middleware(req: NextRequest) { const path = req.nextUrl.pathname const isProtectedRoute = protectedRoutes.includes(path) const isPublicRoute = publicRoutes.includes(path) const cookie = (await cookies()).get('session')?.value const session = await decrypt(cookie) // 沒登入想進 dashboard?請回去 if (isProtectedRoute && !session?.userId) { return NextResponse.redirect(new URL('/login', req.nextUrl)) } // 已經登入還跑去 login 頁?幫你導過去 if (isPublicRoute && session?.userId && !req.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/dashboard', req.nextUrl)) } return NextResponse.next() } export const config = { matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], }

這裡有幾個重點:

  • NextResponse.redirect() 可以把使用者導去別的頁面
  • NextResponse.next() 代表放行,讓 request 繼續往下走
  • config.matcher 決定哪些路徑要經過 Middleware,上面那串 regex 的意思是「除了 API route、靜態檔案、圖片以外的所有路徑」

除了 redirect,還能幹嘛

Middleware 能做的事比 Rewrites 多很多:

import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // 改 request header const requestHeaders = new Headers(request.headers) requestHeaders.set('x-request-id', crypto.randomUUID()) const response = NextResponse.next({ request: { headers: requestHeaders }, }) // 改 response header response.headers.set('x-response-time', Date.now().toString()) return response }

讀寫 Header、讀寫 Cookie、動態 Rewrite、直接回 Response... 基本上在 request 層級能做的事它都能做。


4. Next.js 16 的改名:Middleware → Proxy

這大概是升級 Next.js 16 最容易踩到的坑之一。

Next.js 15 以前Next.js 16+
檔案名稱middleware.tsproxy.ts
函式名稱middleware()proxy()
RuntimeEdge RuntimeNode.js Runtime
設定名稱skipMiddlewareUrlNormalizeskipProxyUrlNormalize

改名這件事我覺得可以理解,因為它本質上就是一個站在 client 跟 server 之間的代理人,叫 Proxy 確實比 Middleware 更精準。

但比較大的改變是 Edge Runtime 不再支援了。以前 Middleware 跑在 Edge 上,有些 Node.js 的 API 不能用。現在改成跑在 Node.js Runtime,反而限制更少了,不過如果你之前有依賴 Edge Runtime 的特性,就要注意一下。

升級的話官方有提供 codemod:

npx @next/codemod@latest upgrade

它會幫你把 middleware.ts 改名成 proxy.ts,函式名稱跟相關的設定也會一起更新。我自己跑過一次,基本上沒什麼問題 (´・ω・`)


5. 一個請求到底走過什麼路

把上面講的東西串起來,一個 request 進到 Next.js 之後大概是這樣走的:

Client 發出 Request ↓ Proxy / Middleware (程式碼層:auth 驗證、加 header、redirect) ↓ Rewrites (設定檔層:路徑轉發) ↓ Next.js Page / API Route (實際處理請求的地方) ↓ Response 回到 Client

Proxy/Middleware 先跑,Rewrites 後跑。所以你可以在 Proxy 裡面先驗證身分、加上 x-user-id 之類的 header,然後 Rewrites 再把 /api/* 轉發到後端服務,後端就可以直接從 header 拿到使用者資訊。

兩個各做各的事,不衝突。


6. 所以到底什麼時候用哪個

寫到這裡其實蠻明顯的了:

  • 如果你的需求是「把路徑 A 對應到路徑 B」,用 Rewrites 就夠了,不用寫程式碼
  • 如果你需要「根據條件做判斷、改 header、改 cookie」,用 Middleware/Proxy
  • 兩個可以同時用,它們本來就是設計來搭配的

以後有人問你 Next.js 的 Proxy 跟 Rewrites 差在哪的時候,可以跟他說:一個是設定檔層級的路由轉發,一個是程式碼層級的請求攔截,就這樣 ヽ(✿゚▽゚)ノ