Files
postgres/src/proxy.ts
2026-01-08 01:24:38 +00:00

89 lines
2.6 KiB
TypeScript

import type { NextFetchEvent, NextRequest } from 'next/server';
import { detectBot } from '@arcjet/next';
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
import createMiddleware from 'next-intl/middleware';
import { NextResponse } from 'next/server';
import arcjet from '@/libs/Arcjet';
import { routing } from './libs/I18nRouting';
const handleI18nRouting = createMiddleware(routing);
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/:locale/dashboard(.*)',
]);
const isAuthPage = createRouteMatcher([
'/sign-in(.*)',
'/:locale/sign-in(.*)',
'/sign-up(.*)',
'/:locale/sign-up(.*)',
]);
// Admin routes that should bypass i18n routing
const ADMIN_ROUTE_PREFIX = '/admin';
const ADMIN_API_PREFIX = '/api/admin';
// Improve security with Arcjet
const aj = arcjet.withRule(
detectBot({
mode: 'LIVE',
// Block all bots except the following
allow: [
// See https://docs.arcjet.com/bot-protection/identifying-bots
'CATEGORY:SEARCH_ENGINE', // Allow search engines
'CATEGORY:PREVIEW', // Allow preview links to show OG images
'CATEGORY:MONITOR', // Allow uptime monitoring services
],
}),
);
export default async function proxy(
request: NextRequest,
event: NextFetchEvent,
) {
// Skip i18n routing for admin and API routes
if (request.nextUrl.pathname.startsWith(ADMIN_ROUTE_PREFIX)
|| request.nextUrl.pathname.startsWith(ADMIN_API_PREFIX)) {
return NextResponse.next();
}
// Verify the request with Arcjet
// Use `process.env` instead of Env to reduce bundle size in middleware
if (process.env.ARCJET_KEY) {
const decision = await aj.protect(request);
if (decision.isDenied()) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
}
// Clerk keyless mode doesn't work with i18n, this is why we need to run the middleware conditionally
if (
isAuthPage(request) || isProtectedRoute(request)
) {
return clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
const locale = req.nextUrl.pathname.match(/(\/.*)\/dashboard/)?.at(1) ?? '';
const signInUrl = new URL(`${locale}/sign-in`, req.url);
await auth.protect({
unauthenticatedUrl: signInUrl.toString(),
});
}
return handleI18nRouting(req);
})(request, event);
}
return handleI18nRouting(request);
}
export const config = {
// Match all pathnames except for
// - … if they start with `/_next`, `/_vercel` or `monitoring`
// - … the ones containing a dot (e.g. `favicon.ico`)
matcher: '/((?!_next|_vercel|monitoring|.*\\..*).*)',
};