PhoenixKitWebAnalytics (PhoenixKitWebAnalytics v0.2.0)

Copy Markdown View Source

Privacy-first web analytics for PhoenixKit — the numbers a hosted analytics product gives you, without the script tag.

What makes it different

Tracking happens server-side, in PhoenixKitWebAnalytics.Plug. One line in the host's browser pipeline counts every HTML response:

pipeline :browser do
  # … existing plugs …
  plug PhoenixKitWebAnalytics.Plug
end

From there:

  • Nothing is added to your pages. No script tag, no bundle to download, no render-blocking request, no third-party domain. Page weight and Core Web Vitals are exactly what they were.
  • No cookies, no consent banner. Visitors are identified by a salted hash of IP + User-Agent that rotates daily and is never stored in reversible form — see PhoenixKitWebAnalytics.Visitor. No IP address is written to the database.
  • Ad blockers can't remove it. There is no client-side request to block, so the numbers are the server's, not a script's.
  • The data is yours. It lives in two tables in the host's own database.

For LiveView navigation (push_patch / push_navigate), add PhoenixKitWebAnalytics.LiveHook to the live_session. For custom events from the browser, there is an optional ~300-byte inline snippet — PhoenixKitWebAnalytics.Web.Beacon — that is off by default.

Reports

Six admin pages under Web Analytics: an overview with the trend and headline numbers, pages, acquisition (referrers / channels / campaigns), technology (browsers, systems, devices, countries), custom events with a live feed, and settings. Everything they show comes from PhoenixKitWebAnalytics.Reports, which is a plain module you can call from your own code:

import PhoenixKitWebAnalytics.Reports

"30d" |> then(&filter(period: &1)) |> top_paths(limit: 20)

Installation

# host mix.exs
{:phoenix_kit_web_analytics, "~> 0.2"}

Then mix deps.get and mix phoenix_kit.update (creates phoenix_kit_web_analytics_events and phoenix_kit_web_analytics_daily_stats), add the plug, and enable the module on the admin Modules page.

Data growth

This is the one PhoenixKit table that grows with traffic rather than with content. PhoenixKitWebAnalytics.Retention rolls completed days into daily totals and prunes raw events past the retention window (365 days by default), so the trend line is permanent while the row count is bounded.

Summary

Functions

Sidebar entries. Routes come from route_module/0, so no tab carries a :live_view — these are navigation and active-state anchors only.

Background workers: the task supervisor that absorbs writes off the request path, and the hourly rollup/prune pass.

Turns tracking on, generating the visitor-hash salt if this is a first enable — so the very first request already hashes against a real secret.

Whether tracking is on.

Summary shown on the admin Modules page.

The events + daily stats tables (run by mix phoenix_kit.update).

Six admin pages plus the public collection endpoints.

Records a custom event from server-side code.

Records a page view from server-side code.

Functions

admin_tabs()

Sidebar entries. Routes come from route_module/0, so no tab carries a :live_view — these are navigation and active-state anchors only.

children()

Background workers: the task supervisor that absorbs writes off the request path, and the hourly rollup/prune pass.

enable_system()

Turns tracking on, generating the visitor-hash salt if this is a first enable — so the very first request already hashes against a real secret.

enabled?()

Whether tracking is on.

Defensive against the DB being unavailable (boot ordering, a test sandbox owner that just stopped): every failure path answers false, so the plug treats "we don't know" as "don't track".

get_config()

Summary shown on the admin Modules page.

migration_module()

The events + daily stats tables (run by mix phoenix_kit.update).

route_module()

Six admin pages plus the public collection endpoints.

track_event(name, attrs \\ %{})

@spec track_event(String.t(), map()) :: :ok

Records a custom event from server-side code.

Use this for things that happen in your context modules rather than in the browser — an order placed, a subscription upgraded — where the event is a fact the server already knows and shouldn't depend on the client to report.

PhoenixKitWebAnalytics.track_event("order.placed", %{
  path: "/checkout",
  metadata: %{"total_cents" => 4900},
  user_uuid: user.uuid
})

attrs accepts any key from PhoenixKitWebAnalytics.Collector's hit shape. Returns :ok immediately; the write happens in a supervised task.

Without :ip and :user_agent the event still records, but under a visitor hash that won't match that person's page views — pass conn values through when the event happens inside a request and you want it attributed to the same visitor.

track_pageview(attrs)

@spec track_pageview(map()) :: :ok

Records a page view from server-side code.

Only needed for pages the plug can't see — a response rendered by something other than the browser pipeline. Ordinary pages are already counted.