Umami never received data: the client-side path (browser beacon to /stats/api/send) has been dead in production. Pageviews are now classified and recorded entirely on the server in the request proxy; no tracking script ships to the browser anymore. - Pure domain decision (decidePageview) filters real page views from assets, infrastructure paths, prefetches and bot user agents - Pure payload builder maps facts onto the Umami send contract as an unnamed event, with the client identity travelling in headers - proxy.ts wires request facts into the domain and fires the send without delaying the response; configured via UMAMI_WEBSITE_ID and UMAMI_API_ENDPOINT - Removes the dead client analytics stack (components, adapters, /stats proxy route) and its layout mount
37 lines
1.2 KiB
TypeScript
37 lines
1.2 KiB
TypeScript
/**
|
|
* Imperative shell for page view analytics: sends one page view to the
|
|
* Umami collection API, identifying the visitor via request headers.
|
|
* Fire-and-forget by design — analytics must never delay a response.
|
|
*/
|
|
|
|
import type { PageviewFacts } from "@/src/domain/pageview-decision";
|
|
import { buildUmamiPageviewBody } from "@/src/domain/umami-payload";
|
|
|
|
const SEND_TIMEOUT_MS = 3_000;
|
|
|
|
const websiteId = process.env.UMAMI_WEBSITE_ID ?? "";
|
|
const apiEndpoint = (process.env.UMAMI_API_ENDPOINT ?? "").replace(/\/+$/, "");
|
|
|
|
export function recordPageviewToUmami(facts: PageviewFacts): void {
|
|
if (!websiteId || !apiEndpoint) return;
|
|
|
|
const body = buildUmamiPageviewBody(facts, websiteId);
|
|
|
|
void fetch(`${apiEndpoint}/api/send`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"User-Agent": facts.userAgent,
|
|
"X-Forwarded-For": facts.clientIp,
|
|
"Accept-Language": facts.language,
|
|
},
|
|
body: JSON.stringify(body),
|
|
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
}).catch((error: unknown) => {
|
|
console.error("umami pageview send failed", {
|
|
url: facts.url,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
});
|
|
});
|
|
}
|