/ middleware.ts
middleware.ts
1 import { NextResponse } from 'next/server'; 2 import type { NextRequest } from 'next/server'; 3 4 const SUPPORTED_LOCALES = ['en', 'ro', 'de', 'fr', 'es', 'it']; 5 const DEFAULT_LOCALE = 'en'; 6 7 function detectLocale(request: NextRequest): string { 8 // 1. Check locale cookie 9 const cookieLocale = request.cookies.get('locale')?.value; 10 if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale)) return cookieLocale; 11 12 // 2. Check Accept-Language header 13 const acceptLang = request.headers.get('Accept-Language'); 14 if (acceptLang) { 15 const preferred = acceptLang 16 .split(',') 17 .map(part => part.split(';')[0].trim().split('-')[0]) 18 .find(lang => SUPPORTED_LOCALES.includes(lang)); 19 if (preferred) return preferred; 20 } 21 22 return DEFAULT_LOCALE; 23 } 24 25 export function middleware(request: NextRequest) { 26 const response = NextResponse.next(); 27 28 // Locale detection — set header for layout.tsx to read 29 const locale = detectLocale(request); 30 response.headers.set('x-locale', locale); 31 32 // Security headers 33 response.headers.set('X-Frame-Options', 'DENY'); 34 response.headers.set('X-Content-Type-Options', 'nosniff'); 35 response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); 36 response.headers.set( 37 'Permissions-Policy', 38 'camera=(), microphone=(), geolocation=(), interest-cohort=()' 39 ); 40 // CSP: unsafe-eval is required by Next.js in development but kept in production 41 // because some Next.js runtime features (dynamic imports, HMR in dev) rely on it. 42 // TODO: test removing unsafe-eval after upgrading to Next.js 15+ which may not need it. 43 response.headers.set( 44 'Content-Security-Policy', 45 [ 46 "default-src 'self'", 47 "script-src 'self' 'unsafe-inline' 'unsafe-eval'", 48 "style-src 'self' 'unsafe-inline'", 49 "img-src 'self' data: blob: https:", 50 "font-src 'self' data:", 51 "connect-src 'self' https://*.supabase.co https://api.anthropic.com https://*.anthropic.com data: blob:", 52 "worker-src blob:", 53 "frame-ancestors 'none'", 54 "base-uri 'self'", 55 "form-action 'self'", 56 "upgrade-insecure-requests", 57 ].join('; ') 58 ); 59 response.headers.set( 60 'Strict-Transport-Security', 61 'max-age=63072000; includeSubDomains; preload' 62 ); 63 64 return response; 65 } 66 67 export const config = { 68 matcher: [ 69 // Match all paths except static files and Next.js internals 70 '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|xml|txt|json)).*)', 71 ], 72 };