fix(web): remove Google Analytics from the site

The root layout loaded the gtag script for measurement ID G-7NBBNJNNQ7,
and the privacy page described it. Remove both. The privacy page now says
the site runs no analytics or tracking scripts, sets no cookies, and loads
its header and footer logos from bongbetic.com.

A jest test scans web/src, web/public and the next.config files for
googletagmanager, google-analytics, gtag( and G- measurement IDs, so the
tag cannot return unnoticed.

Refs #16
This commit is contained in:
2026-11-06 19:54:30 +05:30
parent 180cd4d512
commit 8e8a451ba2
3 changed files with 59 additions and 22 deletions
@@ -0,0 +1,51 @@
import fs from 'fs';
import path from 'path';
// Ticket #16: the site loads no analytics. Everything the static export is
// built from (web/src, web/public and the next.config files) is scanned byte
// for byte, so a measurement tag cannot return through a component, an asset
// or a config file. Binary assets are read as latin1 so every byte is covered.
const WEB_ROOT = process.cwd();
const THIS_FILE = path.join(WEB_ROOT, 'src', 'app', '__tests__', 'no-analytics.test.ts');
const TREE_ROOTS = [path.join(WEB_ROOT, 'src'), path.join(WEB_ROOT, 'public')];
const MARKERS: [string, RegExp][] = [
['googletagmanager', /googletagmanager/],
['google-analytics', /google-analytics/],
['gtag(', /gtag\(/],
['G- measurement ID', /G-[A-Z0-9]{8,}/],
];
function scanTargets(): string[] {
const targets: string[] = [];
for (const root of TREE_ROOTS) {
for (const rel of fs.readdirSync(root, { encoding: 'utf8', recursive: true })) {
const full = path.join(root, rel);
if (fs.statSync(full).isFile()) targets.push(full);
}
}
for (const name of fs.readdirSync(WEB_ROOT)) {
if (/^next\.config\./.test(name)) targets.push(path.join(WEB_ROOT, name));
}
return targets.filter((file) => file !== THIS_FILE);
}
const sources = scanTargets().map((file) => ({
file: path.relative(WEB_ROOT, file),
text: fs.readFileSync(file, 'latin1'),
}));
describe('no analytics on the site (ticket #16)', () => {
it('scans the source, public assets and next.config files', () => {
const names = sources.map((source) => source.file);
expect(names).toEqual(
expect.arrayContaining(['src/app/layout.tsx', 'next.config.js', 'public/_headers']),
);
});
it.each(MARKERS)('contains no %s marker', (_label, marker) => {
const offenders = sources.filter((source) => marker.test(source.text)).map((source) => source.file);
expect(offenders).toEqual([]);
});
});
-13
View File
@@ -1,7 +1,6 @@
import "@/styles/globals.css";
import React from "react";
import { type Metadata } from "next";
import Script from "next/script";
import {
absoluteUrl,
DEFAULT_OG_IMAGE_ALT,
@@ -173,18 +172,6 @@ export default function RootLayout({
/>
</head>
<body>
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-7NBBNJNNQ7"
strategy="lazyOnload"
/>
<Script id="google-analytics" strategy="lazyOnload">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-7NBBNJNNQ7');
`}
</Script>
{children}
</body>
</html>
+8 -9
View File
@@ -24,7 +24,7 @@ export default function PrivacyPage() {
headline: "Voculinux Privacy Policy",
description:
"How Voculinux handles voice data: local engines on your machine, optional Remote API to a server you configure.",
dateModified: "2026-09-23",
dateModified: "2026-11-06",
author: {
"@type": "Person",
name: "Jatin K Malik",
@@ -158,14 +158,13 @@ export default function PrivacyPage() {
</h2>
<div className="space-y-4 text-muted-foreground">
<p>
voculinux.com loads Google Analytics via the googletagmanager gtag
script for measurement ID{" "}
<code className="rounded bg-muted px-1.5 py-0.5">G-7NBBNJNNQ7</code>{" "}
and runs gtag config with no IP anonymization and no cookie flags.
That sends page views and a Google Analytics client identifier (a
cookie) to Google. Google processes IP addresses and device and
browser data. The installed Voculinux app does not send usage
telemetry.
voculinux.com has no analytics, measurement or tracking scripts,
and its code sets no cookies.
</p>
<p>
The Bongbetic logos in the header and footer are images served from
bongbetic.com. Loading any page therefore makes a request to that
host.
</p>
</div>
</article>