/** * Next.js Middleware - i18n + Hydration 防護 * ========================================== * Phase D #17: 修復 i18n 語系切換 Hydration 當機 * * 問題: Client/Server 渲染語系落差導致 Hydration Mismatch * 解法: 強制綁定 NEXT_LOCALE Cookie,確保一致性 * * 版本: v1.1 * 建立: 2026-03-31 (台北時區) * 建立者: Claude Code * * @see QA Report 3.1 節 - i18n 語系切換報錯 */ import createMiddleware from 'next-intl/middleware' import { NextResponse, type NextRequest } from 'next/server' import { routing } from './i18n/routing' // 建立 next-intl middleware const intlMiddleware = createMiddleware(routing) /** * 強制綁定 NEXT_LOCALE Cookie * * 確保 Server/Client 語系一致,避免 Hydration Mismatch */ export default function middleware(request: NextRequest) { // 先執行 next-intl middleware const response = intlMiddleware(request) // 從 URL 路徑提取當前語系 const pathname = request.nextUrl.pathname const localeFromPath = routing.locales.find( (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ) // 決定最終語系 (優先使用路徑中的語系) const finalLocale = localeFromPath || routing.defaultLocale // 🎯 Phase D #17: 強制設定 NEXT_LOCALE cookie // 確保 Client-side Hydration 時使用相同語系 if (response instanceof NextResponse) { const existingLocale = request.cookies.get('NEXT_LOCALE')?.value // 只有當 cookie 不存在或不一致時才設定 if (existingLocale !== finalLocale) { response.cookies.set('NEXT_LOCALE', finalLocale, { path: '/', maxAge: 60 * 60 * 24 * 365, // 1 年 sameSite: 'lax', }) } } return response } export const config = { // 匹配所有路徑,除了以下例外: // - api 路由 // - _next 靜態檔案 // - 靜態資源 (images, fonts, etc.) matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'], }