/** middleware.ts */ export const runtime = 'nodejs'; import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import acceptLanguage from 'accept-language'; import { fallbackLng, languages, cookieName } from '@/i18n/settings'; acceptLanguage.languages(languages); export function middleware(request: NextRequest) { /** * 1. 쿠키에서 언어 가져오기 */ let lng = request.cookies.get(cookieName)?.value; /** * 2. 쿠키가 없다면 브라우저의 Accept-Language 헤더에서 언어를 추론 */ if (!lng) { const headerLang = request.headers.get('accept-language'); lng = acceptLanguage.get(headerLang) || fallbackLng; } const { pathname, searchParams, origin } = request.nextUrl; /** * 3. "/"" 경로로 들어온 경우 -> "/{lng}"로 리다이렉트 * (예) / -> /en, / -> /ko ... */ if (pathname === '/') { const redirectUrl = new URL(`/${lng}`, origin); // 쿼리 파라미터가 있을 경우도 붙여주기 redirectUrl.search = searchParams.toString(); return NextResponse.redirect(redirectUrl); } /** * 4. 현재 pathname이 언어 경로를 포함하고 있는지 확인 * - /en * - /en/... (다른 하위 경로들) * - /ko * - /ko/... */ const hasValidLngInPath = languages.some( (language) => pathname === `/${language}` || pathname.startsWith(`/${language}/`), ); /** * 5. 언어 경로가 누락된 경우 -> "/{lng}" + 기존 pathname 으로 리다이렉트 * 예) /dashboard -> /en/dashboard */ if (!hasValidLngInPath) { const redirectUrl = new URL(`/${lng}${pathname}`, origin); redirectUrl.search = searchParams.toString(); return NextResponse.redirect(redirectUrl); } /** * 6. 위 조건에 걸리지 않았다면 그대로 Next.js로 넘긴다. */ const response = NextResponse.next(); /** * 7. 쿠키에 저장된 언어와 현재 lng가 다르면 업데이트 */ const currentCookie = request.cookies.get(cookieName)?.value; if (lng && lng !== currentCookie) { response.cookies.set(cookieName, lng, { path: '/' }); } return response; } /** * 8. (선택) 매칭할 경로 설정 * - _next, 정적파일(.*), API 등의 경로는 제외시키는 예시 */ export const config = { matcher: [ '/((?!_next|.*\\..*|api).*)', ], };