Next.js Proxy (取代 Middleware)

在 Next.js 的演進過程中,Request 攔截層曾經歷過多次變革。從早期的 _middlewaremiddleware.ts,而在 Next.js 16 之後,官方正式推出了 proxy.ts 來取代舊有的 Middleware 機制。

這次改動不僅是更名,更是為了明確區分「代理 (Proxy)」與「後端邏輯」的邊界,並解決了舊版 Middleware 可能造成的安全性隱患 (CVE-2025-29927)。

為什麼要從 Middleware 變成 Proxy?

  1. 安全性 (Security)middleware.ts 過去容易因為設定不當(例如 Regex 寫錯),導致惡意請求繞過驗證直接打進 API。proxy.ts 採用更嚴格的 "Proxy-first" 設計。
  2. 語意明確 (Semantics):舊名稱容易讓開發者誤以為它是 Express 裡的 Middleware(可以做繁重的運算),但實際上它運行在 Edge Runtime,應該只負責輕量的 路由、轉址、標頭處理。proxy.ts 的命名清楚地傳達了它的職責:它是應用程式的大門

基本設定 (Setup)

proxy.ts 必須放在專案的根目錄下(如果是使用 src 目錄則是 src/proxy.ts),與 apppages 同層級。

基礎語法

我們使用新的 nextProxy 函式來定義處裡邏輯:

// src/proxy.ts
import { nextProxy } from 'next/server';

export default nextProxy({
  async handle(request) {
    console.log('收到請求:', request.nextUrl.pathname);

    // 放行請求,繼續往後端送
    return nextProxy.next();
  },
});

// 定義匹配路徑 (Matcher)
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

核心 API 操作 (API Operations)

nextProxy 的設計核心在於 Request -> Processing -> Response 的流水線處理。根據你想要修改的是「發出去的請求」還是「回傳的回應」,寫法會有所不同。

A. 修改請求 (Request Modification)

如果你想要在請求發送給後端 API 或 Page 之前,注入一些資訊(例如:使用者所在的國家代碼),你需要修改 Request Headers。

關鍵語法:透過 nextProxy.next({ request: { headers } }) 傳遞修改後的標頭。

// src/proxy.ts
import { nextProxy } from 'next/server';

export default nextProxy({
  async handle(request) {
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-user-country', 'TW');

    // � 將修改後的 Headers "往下傳" 給後端
    return nextProxy.next({
      request: {
        headers: requestHeaders,
      },
    });
  },
});

B. 修改回應 (Response Modification)

如果你想要在後端處理完畢,準備回傳給使用者瀏覽器時,注入 Cookies 或 Security Headers,你需要處理 Response 物件。

關鍵語法await nextProxy.next() 會執行請求並回傳 Response 物件,你可以修改這個物件後再 return。

// src/proxy.ts
import { nextProxy } from 'next/server';

export default nextProxy({
  async handle(request) {
    // 1. 執行請求,並等待後端回應
    const response = await nextProxy.next();

    // 2. 修改 Response Headers (例如加入安全標頭)
    response.headers.set('X-Frame-Options', 'DENY');

    // 3. 設定 Cookies (注意:這是寫在 Response 上)
    response.cookies.set('visited', 'true', { path: '/' });

    // 4. 回傳最終回應給瀏覽器
    return response;
  },
});

C. 轉址與重寫 (Redirect & Rewrite)

  • 轉址 (Redirect):瀏覽器網址列 會改變
    return Response.redirect(new URL('/login', request.url));
    
  • 重寫 (Rewrite):瀏覽器網址列 不變,內容被替換。
    return nextProxy.rewrite(new URL('/proxy/target', request.url));
    

D. 完整範例:身份驗證與標頭注入

結合上述觀念,一個常見的生產環境 Proxy 如下:

// src/proxy.ts
import { nextProxy } from 'next/server';

export default nextProxy({
  async handle(request) {
    const { pathname } = request.nextUrl;

    // 1. 檢查權限 (Redirect)
    if (pathname.startsWith('/dashboard')) {
      const token = request.cookies.get('auth_token');
      if (!token) {
        return Response.redirect(new URL('/login', request.url));
      }
    }

    // 2. 注入 Request Headers (給後端看)
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-request-start', Date.now().toString());

    // 3. 取得 Response (可以帶入修改過的 Request)
    const response = await nextProxy.next({
      request: { headers: requestHeaders },
    });

    // 4. 注入 Response Headers (給前端看)
    response.headers.set('x-powered-by', 'Next.js Proxy');

    return response;
  },
});

從 middleware.ts 遷移

如果你的專案還在用舊的 middleware.ts,Next.js 提供了自動化工具協助遷移:

npx @next/codemod@canary middleware-to-proxy

這個指令會自動:

  1. middleware.ts 重新命名為 proxy.ts
  2. export function middleware 轉換為 export default nextProxy 結構。
  3. 更新相關的型別定義。

常見問題

Q: Proxy 中可以使用資料庫嗎?

不建議proxy.ts 仍然運行在 Edge Runtime,這意味著它沒有完整的 Node.js 環境,且任何延遲都會直接阻斷使用者的請求 (Blocking)。複雜的邏輯請留在 Route Handlers (route.ts) 或 Server Actions 中處理。

Q: Matcher 的規則有變嗎?

沒有變。config.matcher 的語法與舊版 Middleware 完全相容,你依然可以使用 Regex 來排除靜態資源檔案。