返回 CodeWhale
middleware.ts
根目录 / web / middleware.ts
1 import { NextRequest, NextResponse } from "next/server";
2 import { locales } from "@/lib/i18n/config";
3 import { detectLocaleFromHeaders } from "@/lib/i18n/detect";
4
5 const COOKIE = "NEXT_LOCALE";
6
7 const SECURITY_HEADERS: Record<string, string> = {
8 "X-Frame-Options": "DENY",
9 "X-Content-Type-Options": "nosniff",
10 "Referrer-Policy": "strict-origin-when-cross-origin",
11 "Permissions-Policy": "camera=(), microphone=(), geolocation=(), interest-cohort=()",
12 "Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
13 };
14
15 function applySecurityHeaders(res: NextResponse): NextResponse {
16 for (const [k, v] of Object.entries(SECURITY_HEADERS)) res.headers.set(k, v);
17 return res;
18 }
19
20 export function middleware(req: NextRequest) {
21 const { pathname } = req.nextUrl;
22
23 // Skip API routes, static files, _next, and the dot-less metadata route
24 // for the shared OG image (but still apply security headers).
25 if (
26 pathname.startsWith("/api/") ||
27 pathname.startsWith("/_next/") ||
28 pathname === "/opengraph-image" ||
29 pathname.includes(".")
30 ) {
31 return applySecurityHeaders(NextResponse.next());
32 }
33
34 // Check if locale is already in path
35 const seg = pathname.split("/")[1];
36 if (locales.includes(seg as typeof locales[number])) {
37 const res = NextResponse.next();
38 res.cookies.set(COOKIE, seg, { path: "/", maxAge: 60 * 60 * 24 * 365 });
39 return applySecurityHeaders(res);
40 }
41
42 // Redirect bare paths to the detected locale (deterministic: cookie, then
43 // Accept-Language full-tag/primary-subtag matching, then the default).
44 const locale = detectLocaleFromHeaders(
45 req.cookies.get(COOKIE)?.value,
46 req.headers.get("accept-language"),
47 );
48 const url = req.nextUrl.clone();
49 url.pathname = `/${locale}${pathname}`;
50 const res = NextResponse.redirect(url);
51 res.cookies.set(COOKIE, locale, { path: "/", maxAge: 60 * 60 * 24 * 365 });
52 return applySecurityHeaders(res);
53 }
54
55 export const config = {
56 // Match everything so security headers apply globally; the function
57 // bypasses redirect/locale logic for /_next, /api, and dotted paths.
58 matcher: ["/:path*"],
59 };
60
60 lines TYPESCRIPT