analytics: config-driven Umami tag + accurate privacy disclosure; fix @types/react v18/v19 dup that broke the build at HEAD

This commit is contained in:
jwinterm
2026-06-12 13:24:54 -04:00
parent e3cc38bded
commit 468eaa1d60
5 changed files with 49 additions and 14 deletions
+2
View File
@@ -0,0 +1,2 @@
NEXT_PUBLIC_UMAMI_HOST=https://stats.suchshop.lol
NEXT_PUBLIC_UMAMI_WEBSITE_ID=62a3259a-8293-4e11-901c-d7fb86d30847
+2
View File
@@ -4,6 +4,7 @@ import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider"; import { ThemeProvider } from "@/components/theme-provider";
import { DynamicFavicon } from "@/components/dynamic-favicon"; import { DynamicFavicon } from "@/components/dynamic-favicon";
import { Footer } from "@/components/layout/footer"; import { Footer } from "@/components/layout/footer";
import { UmamiAnalytics } from "@/components/UmamiAnalytics";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@@ -119,6 +120,7 @@ export default function RootLayout({
disableTransitionOnChange disableTransitionOnChange
> >
<DynamicFavicon /> <DynamicFavicon />
<UmamiAnalytics />
<div className="relative z-10 flex flex-col min-h-screen"> <div className="relative z-10 flex flex-col min-h-screen">
{children} {children}
<Footer /> <Footer />
+21 -13
View File
@@ -22,7 +22,7 @@ export default function PrivacyPage() {
<h1 className="text-4xl font-bold mb-8">Privacy Policy</h1> <h1 className="text-4xl font-bold mb-8">Privacy Policy</h1>
<div className="prose dark:prose-invert max-w-none space-y-6"> <div className="prose dark:prose-invert max-w-none space-y-6">
<p className="text-muted-foreground">Last updated: April 9, 2026</p> <p className="text-muted-foreground">Last updated: June 12, 2026</p>
<section> <section>
<h2 className="text-2xl font-semibold mt-8 mb-4">1. Who We Are</h2> <h2 className="text-2xl font-semibold mt-8 mb-4">1. Who We Are</h2>
@@ -51,17 +51,25 @@ export default function PrivacyPage() {
information with third parties. information with third parties.
</p> </p>
<h3 className="text-xl font-medium mt-4 mb-2">Automatically Collected Information</h3> <h3 className="text-xl font-medium mt-4 mb-2">Analytics</h3>
<p> <p>
Our Site does not use analytics services, tracking pixels, or advertising cookies. We use <strong>Umami</strong>, a privacy-respecting, self-hosted web analytics tool, to understand
We do not use Google Analytics, Facebook Pixel, or similar tracking tools on this website. aggregate traffic to our Site (such as which pages are visited and where visitors arrive from).
Umami is <strong>cookieless</strong> and runs entirely on our own infrastructure &mdash; your data is
never sent to a third-party analytics provider or data broker. It does <strong>not</strong> store your
IP address, set tracking cookies, build cross-site profiles, or collect personally identifiable
information. We honor the <strong>Do Not Track</strong> and <strong>Global Privacy Control</strong>
browser signals: if either is enabled, no analytics are collected from your visit.
</p> </p>
<p> <p>
Our hosting infrastructure (Vercel) may collect standard server logs including IP addresses, browser type, We do not use Google Analytics, Facebook Pixel, advertising cookies, or similar third-party tracking tools.
and pages visited. This data is used for security and performance purposes and is subject to{" "} </p>
<a href="https://vercel.com/legal/privacy-policy" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">
Vercel&apos;s Privacy Policy <h3 className="text-xl font-medium mt-4 mb-2">Server Logs</h3>
</a>. <p>
Our self-hosted infrastructure may record standard server logs (including IP addresses, browser type,
and pages visited) for security and performance purposes. These logs are not used to identify or
profile individual visitors and are retained only as long as needed for operational security.
</p> </p>
<h3 className="text-xl font-medium mt-4 mb-2">Cookies and Local Storage</h3> <h3 className="text-xl font-medium mt-4 mb-2">Cookies and Local Storage</h3>
@@ -92,15 +100,15 @@ export default function PrivacyPage() {
<h2 className="text-2xl font-semibold mt-8 mb-4">4. Third-Party Services</h2> <h2 className="text-2xl font-semibold mt-8 mb-4">4. Third-Party Services</h2>
<p>The Site uses the following third-party services:</p> <p>The Site uses the following third-party services:</p>
<ul className="list-disc pl-6 space-y-2"> <ul className="list-disc pl-6 space-y-2">
<li>
<strong>Vercel</strong> — hosting and deployment.{" "}
<a href="https://vercel.com/legal/privacy-policy" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">Privacy Policy</a>
</li>
<li> <li>
<strong>Cloudflare Turnstile</strong> — bot protection on the contact form.{" "} <strong>Cloudflare Turnstile</strong> — bot protection on the contact form.{" "}
<a href="https://www.cloudflare.com/privacypolicy/" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">Privacy Policy</a> <a href="https://www.cloudflare.com/privacypolicy/" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">Privacy Policy</a>
</li> </li>
</ul> </ul>
<p className="mt-4">
Our website analytics (Umami) and hosting are self-hosted on our own infrastructure and are therefore
not third-party services.
</p>
</section> </section>
<section> <section>
@@ -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;
}
+1 -1
View File
@@ -20,7 +20,7 @@
"node": ">=20.0.0" "node": ">=20.0.0"
}, },
"dependencies": { "dependencies": {
"@types/react": "^18.0.0" "@types/react": "^19.2.14"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {