analytics: config-driven cookieless Umami tag (NEXT_PUBLIC_UMAMI_*, GPC+DNT)

This commit is contained in:
jwinterm
2026-06-12 13:50:08 -04:00
parent 0aa19ca6bf
commit c9df923a2f
3 changed files with 30 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
NEXT_PUBLIC_UMAMI_HOST=https://stats.suchshop.lol
NEXT_PUBLIC_UMAMI_WEBSITE_ID=f7bf24bc-836d-434c-81eb-265f1ae67699
+5 -1
View File
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Crimson_Pro, Space_Grotesk } from "next/font/google";
import "./globals.css";
import { UmamiAnalytics } from "../components/UmamiAnalytics";
const spaceGrotesk = Space_Grotesk({
subsets: ["latin"],
@@ -29,7 +30,10 @@ export default function RootLayout({
}) {
return (
<html lang="en" className={`${spaceGrotesk.variable} ${crimsonPro.variable}`}>
<body>{children}</body>
<body>
<UmamiAnalytics />
{children}
</body>
</html>
);
}
+23
View File
@@ -0,0 +1,23 @@
'use client';
import { useEffect } from 'react';
// Self-hosted, cookieless Umami — config via env so nothing is hardcoded.
// Honors Global Privacy Control + Do-Not-Track. No-op unless both env vars are set.
const HOST = process.env.NEXT_PUBLIC_UMAMI_HOST;
const WEBSITE_ID = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
export function UmamiAnalytics() {
useEffect(() => {
if (!HOST || !WEBSITE_ID) return;
const nav = navigator as { globalPrivacyControl?: boolean };
if (nav.globalPrivacyControl) return;
if (document.querySelector(`script[data-website-id="${WEBSITE_ID}"]`)) return;
const s = document.createElement('script');
s.defer = true;
s.src = `${HOST}/script.js`;
s.setAttribute('data-website-id', WEBSITE_ID);
s.setAttribute('data-do-not-track', 'true');
document.head.appendChild(s);
}, []);
return null;
}