/ 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  };