From c9b057b674234b4676ea0badf9715b9b30011ce1 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Tue, 20 Jan 2026 21:10:39 -0300 Subject: [PATCH] feat(analytics): update Google Analytics integration and improve fetch interception - Refactored Google Analytics proxy routes in the Next.js configuration to enhance fallback handling. - Improved fetch interception logic in the RootLayout component for more robust handling of Google Analytics requests, including support for `sendBeacon`. - Updated the analytics script source to align with new API routes for better tracking accuracy. --- app/api/gtm/[id]/route.ts | 47 ++++++++++++++++++++++++++++ app/api/metrics/route.ts | 65 +++++++++++++++++++++++++++++++++++++++ app/layout.tsx | 46 ++++++++++++++++++++++----- next.config.ts | 15 ++------- 4 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 app/api/gtm/[id]/route.ts create mode 100644 app/api/metrics/route.ts diff --git a/app/api/gtm/[id]/route.ts b/app/api/gtm/[id]/route.ts new file mode 100644 index 0000000..7496236 --- /dev/null +++ b/app/api/gtm/[id]/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + const { id } = params; + + try { + const response = await fetch( + `https://www.googletagmanager.com/gtag/js?id=${id}`, + { + headers: { + 'User-Agent': request.headers.get('user-agent') || '', + }, + } + ); + + if (!response.ok) { + return new NextResponse('Script not found', { status: 404 }); + } + + const scriptContent = await response.text(); + + // Modificar URLs internas do script para usar o proxy + const modifiedScript = scriptContent + .replace( + /https?:\/\/[^\/]*google-analytics\.com\/g\/collect/g, + '/api/metrics' + ) + .replace( + /https?:\/\/[^\/]*google-analytics\.com/g, + '/api/metrics' + ); + + return new NextResponse(modifiedScript, { + status: 200, + headers: { + 'Content-Type': 'application/javascript', + 'Cache-Control': 'public, max-age=3600', + }, + }); + } catch (error) { + console.error('GTM proxy error:', error); + return new NextResponse('Proxy error', { status: 500 }); + } +} diff --git a/app/api/metrics/route.ts b/app/api/metrics/route.ts new file mode 100644 index 0000000..22d7981 --- /dev/null +++ b/app/api/metrics/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const url = new URL('https://www.google-analytics.com/g/collect'); + + // Copiar todos os parâmetros da query string + searchParams.forEach((value, key) => { + url.searchParams.append(key, value); + }); + + try { + const response = await fetch(url.toString(), { + method: 'GET', + headers: { + 'User-Agent': request.headers.get('user-agent') || '', + 'Accept-Language': request.headers.get('accept-language') || '', + }, + }); + + return new NextResponse(null, { + status: response.status, + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + }, + }); + } catch (error) { + console.error('Google Analytics proxy error:', error); + return new NextResponse(null, { status: 200 }); // Retornar 200 para não quebrar o tracking + } +} + +export async function POST(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const url = new URL('https://www.google-analytics.com/g/collect'); + + // Copiar todos os parâmetros da query string + searchParams.forEach((value, key) => { + url.searchParams.append(key, value); + }); + + try { + const body = await request.text(); + + const response = await fetch(url.toString(), { + method: 'POST', + headers: { + 'User-Agent': request.headers.get('user-agent') || '', + 'Accept-Language': request.headers.get('accept-language') || '', + 'Content-Type': 'text/plain', + }, + body: body || undefined, + }); + + return new NextResponse(null, { + status: response.status, + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + }, + }); + } catch (error) { + console.error('Google Analytics proxy error:', error); + return new NextResponse(null, { status: 200 }); + } +} diff --git a/app/layout.tsx b/app/layout.tsx index 659ddde..c609db4 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -201,11 +201,19 @@ export default function RootLayout({ children }: Readonly) { dangerouslySetInnerHTML={{ __html: ` (function() { - // Interceptar fetch + // Interceptar fetch - método mais robusto const originalFetch = window.fetch; window.fetch = function(url, options) { - if (typeof url === 'string' && url.includes('google-analytics.com')) { - url = url.replace('https://www.google-analytics.com', '/ga'); + if (typeof url === 'string') { + // Interceptar google-analytics.com/g/collect + if (url.includes('google-analytics.com/g/collect')) { + const urlObj = new URL(url); + url = '/api/metrics' + urlObj.search; + } + // Interceptar outros endpoints do google-analytics.com + else if (url.includes('google-analytics.com')) { + url = url.replace(/https?:\\/\\/[^\\/]*google-analytics\\.com/g, '/api/metrics'); + } } return originalFetch.call(this, url, options); }; @@ -213,18 +221,39 @@ export default function RootLayout({ children }: Readonly) { // Interceptar XMLHttpRequest const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...rest) { - if (typeof url === 'string' && url.includes('google-analytics.com')) { - url = url.replace('https://www.google-analytics.com', '/ga'); + if (typeof url === 'string') { + if (url.includes('google-analytics.com/g/collect')) { + const urlObj = new URL(url); + url = '/api/metrics' + urlObj.search; + } + else if (url.includes('google-analytics.com')) { + url = url.replace(/https?:\\/\\/[^\\/]*google-analytics\\.com/g, '/api/metrics'); + } } return originalOpen.call(this, method, url, ...rest); }; + + // Interceptar sendBeacon (método usado pelo GA4) + const originalSendBeacon = navigator.sendBeacon; + navigator.sendBeacon = function(url, data) { + if (typeof url === 'string') { + if (url.includes('google-analytics.com/g/collect')) { + const urlObj = new URL(url); + return originalSendBeacon.call(this, '/api/metrics' + urlObj.search, data); + } + else if (url.includes('google-analytics.com')) { + url = url.replace(/https?:\\/\\/[^\\/]*google-analytics\\.com/g, '/api/metrics'); + } + } + return originalSendBeacon.call(this, url, data); + }; })(); `, }} /> {/* Google Analytics - Script principal via proxy */}