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) { logger.error('Failed to proxy analytics request', { error: (error as Error).message, }); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); } }