xpeditis2.0/apps/frontend/middleware.ts
David ec0173483a
All checks were successful
Dev CI / Backend — Lint (push) Successful in 10m23s
Dev CI / Backend — Unit Tests (push) Successful in 10m17s
Dev CI / Frontend — Lint & Type-check (push) Successful in 11m3s
Dev CI / Frontend — Unit Tests (push) Successful in 10m33s
Dev CI / Notify Failure (push) Has been skipped
fix language
2026-04-21 18:04:02 +02:00

85 lines
2.6 KiB
TypeScript

/**
* Middleware
*
* Composes:
* 1. next-intl locale detection & prefix enforcement (/fr/..., /en/...)
* 2. Auth protection — redirects unauthenticated users to /{locale}/login
*
* All URLs are locale-prefixed (localePrefix: 'always' in routing.ts).
*/
import createMiddleware from 'next-intl/middleware';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { routing } from './i18n/routing';
const intlMiddleware = createMiddleware(routing);
// Paths that do not require authentication (matched AFTER locale stripping)
const exactPublicPaths = ['/'];
const prefixPublicPaths = [
'/login',
'/register',
'/forgot-password',
'/reset-password',
'/verify-email',
'/about',
'/careers',
'/blog',
'/press',
'/contact',
'/carrier',
'/pricing',
'/docs',
];
function stripLocale(pathname: string): { locale: string | null; pathWithoutLocale: string } {
for (const locale of routing.locales) {
if (pathname === `/${locale}`) return { locale, pathWithoutLocale: '/' };
if (pathname.startsWith(`/${locale}/`)) {
return { locale, pathWithoutLocale: pathname.slice(`/${locale}`.length) };
}
}
return { locale: null, pathWithoutLocale: pathname };
}
export default function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Step 1: let next-intl handle locale redirection (e.g. `/dashboard` → `/fr/dashboard`)
const intlResponse = intlMiddleware(request);
// If next-intl already issued a redirect, honor it (user will land on the
// locale-prefixed URL and the middleware will run again with auth intact).
if (intlResponse.status >= 300 && intlResponse.status < 400) {
return intlResponse;
}
// Step 2: auth protection
const { locale, pathWithoutLocale } = stripLocale(pathname);
const resolvedLocale = locale || routing.defaultLocale;
const isPublicPath =
exactPublicPaths.includes(pathWithoutLocale) ||
prefixPublicPaths.some(
p => pathWithoutLocale === p || pathWithoutLocale.startsWith(p + '/')
);
const token = request.cookies.get('accessToken')?.value;
if (!isPublicPath && !token) {
const loginUrl = new URL(`/${resolvedLocale}/login`, request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
}
return intlResponse;
}
export const config = {
// Exclude Next.js internals, API routes, and static assets
matcher: [
'/((?!_next/static|_next/image|api|assets|favicon\\.ico|manifest\\.json|.*\\.(?:png|jpg|jpeg|gif|webp|svg|ico|mp4|mp3|pdf|txt|xml|csv|json)$).*)',
],
};