diff --git a/app/stats/api/send/route.ts b/app/stats/api/send/route.ts deleted file mode 100644 index 5555522b..00000000 --- a/app/stats/api/send/route.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { getServerAppServices } from '@/lib/services/create-services.server'; -import { config } from '@/lib/config'; - -/** - * Smart Proxy for Umami Analytics. - * - * This Route Handler receives tracking events from the browser, - * injects the secret UMAMI_WEBSITE_ID, and forwards them to the - * internal Umami API endpoint. - * - * This ensures: - * 1. The Website ID is NOT leaked to the client bundle. - * 2. The Umami API endpoint is hidden behind our domain. - * 3. We have full control over the tracking data. - */ -export async function POST(request: NextRequest) { - const services = getServerAppServices(); - const logger = services.logger.child({ component: 'umami-smart-proxy' }); - - try { - const body = await request.json(); - const { type, payload } = body; - - // Inject the secret websiteId from server config - const websiteId = config.analytics.umami.websiteId; - if (!websiteId) { - logger.warn('Umami tracking received but no Website ID configured on server'); - return NextResponse.json({ status: 'ignored' }, { status: 200 }); - } - - // Prepare the enhanced payload with the secret ID - const enhancedPayload = { - ...payload, - website: websiteId, - }; - - const umamiEndpoint = config.analytics.umami.apiEndpoint; - - // Log the event (internal only) - logger.debug('Forwarding analytics event', { - type, - url: payload.url, - website: websiteId.slice(0, 8) + '...', - }); - - const response = await fetch(`${umamiEndpoint}/api/send`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'User-Agent': request.headers.get('user-agent') || 'KLZ-Smart-Proxy', - 'X-Forwarded-For': request.headers.get('x-forwarded-for') || '', - }, - body: JSON.stringify({ type, payload: enhancedPayload }), - }); - - if (!response.ok) { - const errorText = await response.text(); - logger.error('Umami API responded with error', { - status: response.status, - error: errorText.slice(0, 100), - }); - return new NextResponse(errorText, { status: response.status }); - } - - return NextResponse.json({ status: 'ok' }); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - const errorStack = error instanceof Error ? error.stack : undefined; - - // Console error to ensure it appears in logs even if logger fails - console.error('CRITICAL PROXY ERROR:', { - message: errorMessage, - stack: errorStack, - endpoint: config.analytics.umami.apiEndpoint, - }); - - logger.error('Failed to proxy analytics request', { - error: errorMessage, - stack: errorStack, - }); - - return NextResponse.json( - { - error: 'Internal Server Error', - details: errorMessage, // Expose error for debugging - endpoint: config.analytics.umami.apiEndpoint ? 'configured' : 'missing', - }, - { status: 500 }, - ); - } -} diff --git a/next.config.mjs b/next.config.mjs index 79c47cc4..53601920 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -347,6 +347,10 @@ const nextConfig = { } return [ + { + source: '/stats/:path*', + destination: `${umamiUrl}/:path*`, + }, { source: '/cms/:path*', destination: `${directusUrl}/:path*`,