This commit is contained in:
a2a-platform
2026-06-13 11:20:08 +00:00
parent 0fe464c93d
commit 1a1e8db145
9 changed files with 379 additions and 14 deletions

View File

@@ -1,4 +1,5 @@
import "../globals.css";
import { PageLoadTracker } from "../PageLoadTracker.jsx";
import { baseMetadata } from "../seo.js";
export const metadata = baseMetadata;
@@ -6,7 +7,10 @@ export const metadata = baseMetadata;
export default function EnglishLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
<body>
<PageLoadTracker locale="en" />
{children}
</body>
</html>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
import { useEffect } from "react";
export function PageLoadTracker({ locale }) {
useEffect(() => {
const payload = {
path: window.location.pathname,
locale,
referrer: document.referrer || null,
viewport: {
width: window.innerWidth,
height: window.innerHeight,
devicePixelRatio: window.devicePixelRatio || 1,
},
metadata: {
language: navigator.language || null,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || null,
},
};
const body = JSON.stringify(payload);
if (navigator.sendBeacon) {
const blob = new Blob([body], { type: "application/json" });
navigator.sendBeacon("/api/page-load", blob);
return;
}
fetch("/api/page-load", {
method: "POST",
headers: { "content-type": "application/json" },
body,
keepalive: true,
}).catch(() => {});
}, [locale]);
return null;
}

View File

@@ -0,0 +1,71 @@
import { NextResponse } from "next/server";
import pg from "pg";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
const { Pool } = pg;
function pool() {
if (!process.env.DATABASE_URL) {
return null;
}
globalThis.__robertsearesPageLoadPool ||= new Pool({
connectionString: process.env.DATABASE_URL,
max: 2,
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 10_000,
});
return globalThis.__robertsearesPageLoadPool;
}
function text(value, maxLength) {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed ? trimmed.slice(0, maxLength) : null;
}
function object(value) {
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}
export async function POST(request) {
const database = pool();
if (!database) {
return new NextResponse(null, { status: 202 });
}
try {
const body = await request.json();
const path = text(body.path, 512) || "/";
const locale = text(body.locale, 16);
const referrer = text(body.referrer, 2048);
const userAgent = text(request.headers.get("user-agent"), 2048);
const viewport = object(body.viewport);
const metadata = {
...object(body.metadata),
forwardedFor: text(request.headers.get("x-forwarded-for"), 2048),
country:
text(request.headers.get("cf-ipcountry"), 16)
|| text(request.headers.get("x-vercel-ip-country"), 16)
|| text(request.headers.get("x-country-code"), 16),
};
await database.query(
`INSERT INTO robertseares_page_loads
(path, locale, referrer, user_agent, viewport, metadata)
VALUES ($1, $2, $3, $4, $5::jsonb, $6::jsonb)`,
[path, locale, referrer, userAgent, JSON.stringify(viewport), JSON.stringify(metadata)],
);
} catch (error) {
console.warn("page load tracking failed", error);
}
return new NextResponse(null, { status: 204 });
}
export function GET() {
return NextResponse.json({ ok: true });
}

View File

@@ -1,4 +1,5 @@
import "../globals.css";
import { PageLoadTracker } from "../PageLoadTracker.jsx";
import { baseMetadata } from "../seo.js";
export const metadata = {
@@ -16,7 +17,10 @@ export const metadata = {
export default function PortugueseLayout({ children }) {
return (
<html lang="pt-BR">
<body>{children}</body>
<body>
<PageLoadTracker locale="pt" />
{children}
</body>
</html>
);
}