Brandfine Docs
Concepts

Analytics

Self-hosted, per-workspace traffic analytics joined to your CMS content.

Brandfine ships a built-in analytics surface alongside every workspace: visitor counts, top pages, sources, countries, devices, real-time view, and — the differentiating bit — a content × traffic join that links page paths back to the posts that produced them.

The backing store is a self-hosted Umami instance on the same droplet as the CMS. From a customer's perspective it's just "Brandfine analytics" — every report renders inside the CMS, every URL is on the brandfine.co domain, and the data never leaves Brandfine's infrastructure.

What you see in the CMS

Sidebar → Growth → SEO & Analytics. A tile per workspace, each in one of four states:

StateWhat it means
No domainThe workspace hasn't set domain yet. Analytics needs a hostname to scope traffic to.
OffNot enabled for this workspace. Click Enable (owner or admin role) to provision.
WaitingProvisioned but no traffic yet. The card polls every 5s; flips to Live the moment the first pageview arrives.
LiveReceiving traffic. Card shows 7-day visitors/pageviews plus current active visitors. Click anywhere on the tile to drill into the full reports.

The detail page has a range picker (24h / 7d / 30d / 90d) and seven report cards: traffic time-series, top pages, sources, countries, devices, real-time, plus Content performance and Other pages.

Content performance — the wedge

Generic analytics tools see URLs; Brandfine sees URLs + the posts behind them. The Content performance card joins each pageview to a workspace Post by last URL segment → Post.slug, then surfaces:

  • The post title (linked to the editor in the CMS)
  • The post type (blog, services, custom types) as a chip
  • The locale tag for non-default-locale variants
  • View count, with a proportional bar

Pages without a matching post — homepage, section indexes, static routes, anything Brandfine doesn't recognise — land in the Other pages card instead.

The match strategy is conservative: it only looks at the last path segment, so /blog/uk-eta-2026 matches but /products/foo/reviews matches on reviews (probably wrong if the real post is foo). Per-post-type URL templates are a future tightening — see the Phase A design doc for the trade-off note.

Deploy markers on the chart

Every CMS publish writes a PostPublished row with a publishedAt timestamp. The traffic chart overlays those publishes as dashed amber vertical lines so you can see traffic shifts in context. The chart header shows a ● Publishes N pill alongside the Pageviews / Visitors legend; hovering a bucket with publishes lists the post titles in the tooltip.

This works retroactively — you'll see deploy markers for every publish in PostPublished, even ones that pre-date the moment analytics was enabled.

Privacy & data ownership

  • Cookieless. Umami's tracker uses no cookies and stores no personally identifying data — no consent banner is required.
  • No third-party data sharing. Events go directly to the Brandfine droplet, not to Google / Vercel / Plausible / etc.
  • Brandfine is the data controller for every workspace's analytics data. One bill, one host, one privacy policy.
  • Per-workspace isolation. Each workspace maps to one Umami "website"; queries are scoped at the data layer.

Two ways for your site to send pageviews

See the SDK analytics guide for the full walkthrough. The short version:

  • @brandfine/client automatically — call bf.analytics.install() once on the client and the SDK fetches this workspace's tracker config and injects the script. Use the server-component pattern in Next.js / Astro to keep the API key out of the browser bundle (and skip the runtime round-trip).
  • Manual <script> tag — for sites that don't use the SDK, the CMS surfaces a paste-in <script defer src="…" data-website-id="…"> on the workspace detail page under "Manual install snippet".

Once installed, the workspace card flips from Waiting to Live within ~5 seconds.

When this isn't enough

The Phase A architecture wraps Umami behind an AnalyticsProvider abstraction (see @brandfine/analytics in the monorepo). The day we hit a hard ceiling — multi-dimensional cohort queries, regional data residency, ≥ 5–10M events/month per workspace — we'll swap in a ClickHouse-backed provider behind the same interface. Customer code and the CMS UI won't change.

Until that day comes, the simpler stack is the right stack.

On this page