feat(analytics): record pageviews server-side only
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
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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),
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user