Analytics
Classifyre ships optional integrations for two independent analytics providers:
| Provider | Purpose | Configured by |
|---|---|---|
| PostHog | Product analytics — events, funnels, session behaviour | Helm (frontend.posthog.*) |
| Google Analytics 4 | Audience and traffic reporting via gtag.js | Helm (frontend.googleAnalytics.*) |
Both are off by default, can be enabled independently, and coexist without interfering — enabling one does not affect the other.
No data leaves the browser unless you explicitly enable a provider and supply its ID.
How the web app resolves analytics IDs
The web container image is built once in CI and configured per-deployment by Helm. That rules out NEXT_PUBLIC_* variables for this purpose: Next.js inlines those into the JavaScript bundle at build time, so anything Helm sets afterwards is invisible to the browser.
Instead the web container serves the browser-facing analytics config at runtime:
Browser → GET /classifyre-cfg/ → web container reads env → JS assigning window.__CLASSIFYRE_ANALYTICS__- The root layout loads
/classifyre-cfg/synchronously, before React hydrates. - The response is
Cache-Control: no-store, so ahelm upgradetakes effect on the next page load. - When Google Analytics is enabled the same response also carries the standard
gtag.jsbootstrap and injects the Google tag. - Changing a token or measurement ID needs no image rebuild — only a
helm upgrade.
Like /classifyre-usr, the path name is deliberately neutral: paths containing analytics, tracking, or gtag are targeted by ad-blocker filter lists.
This applies to the web app only. The docs site and blog are static exports built by their own pipeline — see Docs site and blog below.
PostHog
How it works
All PostHog traffic is proxied through the web container at /classifyre-usr:
Browser → /classifyre-usr/* → web container → us.i.posthog.com- Requests from the browser go to your own domain first, never directly to
posthog.com. - Ad-blockers that block known analytics domains are bypassed.
- No extra ingress rules or firewall exceptions are needed.
The server-side proxy route reads POSTHOG_INGEST_HOST to determine the upstream endpoint. This env var is never exposed to the browser, so you can safely set it to your PostHog region or a managed reverse proxy CNAME.
Getting a project token
Create a PostHog account
Sign up at posthog.com (cloud) or self-host PostHog on your own infrastructure.
Create a project
In the PostHog dashboard, go to Settings → Projects → New project and give it a name (e.g. classifyre-production).
Copy the project token
Navigate to Settings → Project → Project API key. The token looks like phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Enabling via Helm
values.yaml
frontend:
posthog:
enabled: true
token: "phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" # your PostHog project token
# host defaults to /classifyre-usr (built-in proxy) — no change neededConfiguration reference
| Value | Default | Description |
|---|---|---|
frontend.posthog.enabled | false | Set to true to inject PostHog env vars into the web container. |
frontend.posthog.token | "" | PostHog project token (phc_...). Required when enabled=true. |
frontend.posthog.host | "/classifyre-usr" | Browser-facing ingest path (NEXT_PUBLIC_POSTHOG_HOST). Keep the default to route through the built-in proxy. |
frontend.posthog.uiHost | "https://us.posthog.com" | PostHog UI host for toolbar links (NEXT_PUBLIC_POSTHOG_UI_HOST). Use https://eu.posthog.com for EU Cloud. |
frontend.posthog.ingestHost | "https://us.i.posthog.com" | Server-side upstream endpoint (POSTHOG_INGEST_HOST). Never exposed to the browser. Use https://eu.i.posthog.com for EU Cloud, or your managed reverse proxy CNAME. |
EU Cloud
Set both the UI host and the server-side ingest host for EU Cloud:
frontend:
posthog:
enabled: true
token: "phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
uiHost: "https://eu.posthog.com"
ingestHost: "https://eu.i.posthog.com"Managed reverse proxy (recommended for production)
For even higher event capture rates, set up a PostHog managed reverse proxy on your own subdomain (e.g. e.yourcompany.com). Point ingestHost at the CNAME PostHog provisions for you, and set host to the same subdomain so the browser sends events there directly — bypassing the Next.js proxy hop entirely.
frontend:
posthog:
enabled: true
token: "phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
host: "https://e.yourcompany.com"
uiHost: "https://us.posthog.com"
ingestHost: "https://e.yourcompany.com" # same: proxy handles its own upstreamChoose a subdomain that does not contain words like analytics, tracking, telemetry, or posthog — ad-blockers target these terms.
Google Analytics
Getting a measurement ID
Create a GA4 property
In Google Analytics, go to Admin → Create → Property and complete the setup.
Add a web data stream
Under Admin → Data streams → Add stream → Web, enter the site URL.
Copy the measurement ID
The stream details show a Measurement ID of the form G-XXXXXXXXXX.
Enabling via Helm
values.yaml
frontend:
googleAnalytics:
enabled: true
measurementId: "G-XXXXXXXXXX"The chart fails the render with an explicit error if enabled: true is set without a measurement ID, so a misconfiguration cannot reach the cluster silently.
Configuration reference
| Value | Default | Description |
|---|---|---|
frontend.googleAnalytics.enabled | false | Set to true to inject the Google Analytics measurement ID into the web container. |
frontend.googleAnalytics.measurementId | "" | GA4 measurement ID (G-XXXXXXXXXX), injected as GOOGLE_ANALYTICS_MEASUREMENT_ID. Required when enabled=true. |
The value is validated against ^G-[A-Z0-9]{4,20}$ before being served to the browser; anything else is ignored and no Google tag is loaded.
The web app is a single-page app, so most navigations do not reload the page. GA4 counts these through Enhanced measurement → Page changes based on browser history events, which is on by default. If you turned Enhanced measurement off, only the first page of each visit is counted.
Ad-blockers
Unlike PostHog, Google Analytics is not proxied: gtag.js is loaded directly from googletagmanager.com and hits go to google-analytics.com, both of which are blocked by most ad-blockers and by some corporate DNS. Expect Google Analytics to under-report relative to PostHog. Use PostHog for numbers you intend to rely on.
Docs site and blog
The docs site and blog are static exports deployed separately from the Helm chart (for example on Cloudflare Pages), so their configuration is baked in at build time rather than injected by Helm. Set these variables in the build environment:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_GA_MEASUREMENT_ID | GA4 measurement ID (G-XXXXXXXXXX). Leave unset to disable. |
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN | PostHog project token. Leave unset to disable. |
NEXT_PUBLIC_POSTHOG_HOST | PostHog ingest host, e.g. https://eu.i.posthog.com. |
NEXT_PUBLIC_POSTHOG_UI_HOST | PostHog UI host, e.g. https://eu.posthog.com. |
See apps/docs/.env.example and apps/blog/.env.example. Because these are build-time values, changing them requires a rebuild and redeploy of the affected site.
Disabling analytics
Set enabled: false (the default) for either provider. No env vars are injected, the corresponding SDK never initialises, and no requests are made to any analytics endpoint.
frontend:
posthog:
enabled: false
googleAnalytics:
enabled: falseFor the docs site and blog, leave the corresponding NEXT_PUBLIC_* variables unset at build time.