diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index b7dde6ab..eaee3a6f 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -53,6 +53,25 @@ export default async function LocaleLayout({ messages = {}; } + // Track pageview on the server with high-fidelity header context + const { getServerAppServices } = await import('@/lib/services/create-services.server'); + const serverServices = getServerAppServices(); + + const { headers } = await import('next/headers'); + const requestHeaders = await headers(); + + if ('setServerContext' in serverServices.analytics) { + (serverServices.analytics as any).setServerContext({ + userAgent: requestHeaders.get('user-agent') || undefined, + language: requestHeaders.get('accept-language')?.split(',')[0] || undefined, + referrer: requestHeaders.get('referer') || undefined, + ip: requestHeaders.get('x-forwarded-for')?.split(',')[0] || undefined, + }); + } + + // Track initial server-side pageview + serverServices.analytics.trackPageview(); + return (
diff --git a/app/actions/contact.ts b/app/actions/contact.ts index b3979220..bbd180de 100644 --- a/app/actions/contact.ts +++ b/app/actions/contact.ts @@ -10,6 +10,23 @@ import { getServerAppServices } from '@/lib/services/create-services.server'; export async function sendContactFormAction(formData: FormData) { const services = getServerAppServices(); const logger = services.logger.child({ action: 'sendContactFormAction' }); + + // Set analytics context from request headers for high-fidelity server-side tracking + const { headers } = await import('next/headers'); + const requestHeaders = await headers(); + + if ('setServerContext' in services.analytics) { + (services.analytics as any).setServerContext({ + userAgent: requestHeaders.get('user-agent') || undefined, + language: requestHeaders.get('accept-language')?.split(',')[0] || undefined, + referrer: requestHeaders.get('referer') || undefined, + ip: requestHeaders.get('x-forwarded-for')?.split(',')[0] || undefined, + }); + } + + // Track attempt + services.analytics.track('contact-form-attempt'); + const name = formData.get('name') as string; const email = formData.get('email') as string; const message = formData.get('message') as string; @@ -110,6 +127,11 @@ export async function sendContactFormAction(formData: FormData) { priority: 5, }); + // Track success + services.analytics.track('contact-form-success', { + is_product_request: !!productName, + }); + return { success: true }; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); diff --git a/app/api/feedback/route.ts b/app/api/feedback/route.ts index c1f316d2..d09fabf2 100644 --- a/app/api/feedback/route.ts +++ b/app/api/feedback/route.ts @@ -3,14 +3,14 @@ import { handleFeedbackRequest } from '@mintel/next-feedback'; import { config } from '@/lib/config'; export async function GET(req: NextRequest) { - return handleFeedbackRequest(req, { + return handleFeedbackRequest(req as any, { url: config.infraCMS.url, token: config.infraCMS.token, }); } export async function POST(req: NextRequest) { - return handleFeedbackRequest(req, { + return handleFeedbackRequest(req as any, { url: config.infraCMS.url, token: config.infraCMS.token, }); diff --git a/lib/services/analytics/umami-analytics-service.ts b/lib/services/analytics/umami-analytics-service.ts index a4c6f255..cf70c6d0 100644 --- a/lib/services/analytics/umami-analytics-service.ts +++ b/lib/services/analytics/umami-analytics-service.ts @@ -25,6 +25,12 @@ export class UmamiAnalyticsService implements AnalyticsService { private websiteId?: string; private endpoint: string; private logger: LoggerService; + private serverContext?: { + userAgent?: string; + language?: string; + referrer?: string; + ip?: string; + }; constructor( private readonly options: UmamiAnalyticsServiceOptions, @@ -43,6 +49,19 @@ export class UmamiAnalyticsService implements AnalyticsService { }); } + /** + * Set the server-side context for the current request. + * This allows the service to use real request headers for tracking. + */ + setServerContext(context: { + userAgent?: string; + language?: string; + referrer?: string; + ip?: string; + }) { + this.serverContext = context; + } + /** * Internal method to send the payload to Umami API. */ @@ -63,8 +82,8 @@ export class UmamiAnalyticsService implements AnalyticsService { website: this.websiteId, hostname: isClient ? window.location.hostname : 'server', screen: isClient ? `${window.screen.width}x${window.screen.height}` : undefined, - language: isClient ? navigator.language : undefined, - referrer: isClient ? document.referrer : undefined, + language: isClient ? navigator.language : this.serverContext?.language, + referrer: isClient ? document.referrer : this.serverContext?.referrer, ...data, }; @@ -74,13 +93,28 @@ export class UmamiAnalyticsService implements AnalyticsService { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout + const headers: Record