Everything a visitor does on a LiveView page, recorded server-side from the socket — no client-side script.
PhoenixKitWebAnalytics.Plug sees HTTP requests, which covers the first load
of a page. Once a LiveView is connected, the visitor's clicks, form submits
and navigations travel over the websocket instead, and that's where this hook
sits. It records:
- Page views for live navigation —
push_navigate,push_patch,<.link navigate>/<.link patch>change the URL without an HTTP request, so the plug never sees them. - Interactions — every
phx-click,phx-submit,phx-keydown… event the LiveView handles, by event name ("add_to_cart","save"). Form contents are never recorded; see "What an interaction stores" below. - Leaving — the page is registered with
PhoenixKitWebAnalytics.LivePresence, which records a"leave"event with the time spent on the page when the LiveView process ends, and powers the "on the site right now" view.
Attach it in the host's live_session, after whatever mounts the current
user, so logged-in visitors are attributed:
live_session :public,
on_mount: [
{PhoenixKitWeb.Users.Auth, :phoenix_kit_mount_current_scope},
{PhoenixKitWebAnalytics.LiveHook, :track_navigation}
] do
live "/", HomeLive
live "/pricing", PricingLive
endRequires connect_info on the socket
The hook needs the same two inputs the plug has — client IP and User-Agent — because they feed the daily visitor hash. Without them a LiveView navigation would hash to a different visitor than the page load that preceded it, inflating the visitor count. Rather than record data it knows to be wrong, the hook does nothing unless the endpoint provides them:
socket "/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [:peer_data, :x_headers, :user_agent, session: @session_options]],
longpoll: [connect_info: [:peer_data, :x_headers, :user_agent, session: @session_options]]The keys must be listed on both transports: LiveView falls back to long
polling when a websocket can't be opened (a corporate proxy, a flaky
network), and a visitor on the fallback transport is invisible to this hook
if only websocket: carries them. If :peer_data or :user_agent is
missing the hook stays inert and only full page loads are counted — check
this first if LiveView activity isn't showing up.
:x_headers is what lets the hook see the visitor behind a reverse proxy:
when the socket's peer is a private or loopback address (the proxy), the
visitor's address is the forwarded one, read by the same rule as the plug
(see "Client IP" in PhoenixKitWebAnalytics.Plug). Without :x_headers
such a socket can't name its visitor — a proxy, a container network and
localhost in development look the same — so the hook stays inert for it —
no page view, no interaction, no "Right now" entry — and counts the skip;
Settings then shows a warning. With :x_headers listed and no proxy in
front (development, a LAN), the peer is the visitor. The same headers
carry an x-accept-language, when a proxy sets one, for the visitor's
language.
Not double-counted
A page load is an ordinary HTTP response the plug already recorded, so the
handle_params that follows the connected mount is not counted again. A live
navigation is told apart by LiveView's _live_referer connect parameter,
which the client sends only when it arrived by push_navigate / <.link navigate>. A reconnect (a deploy, a dropped network) remounts with
_mounts > 0 and is never counted — the visitor didn't go anywhere.
What an interaction stores
The event name, the page it happened on, and — only for parameter names
listed in the web_analytics_event_params setting (tab, view, step …
by default) — short scalar values, so "switched to the pricing tab" is
visible. Everything else in the params is discarded before it leaves the
LiveView process: form fields, free text, uploads.
Form typing (phx-change, recognisable by its "_target" parameter) is
not recorded at all, and neither are event names listed in the
web_analytics_ignore_events setting. The same event repeated within a
second on one page counts once, so a double-click or a key held down is one
interaction.
Events handled by a LiveComponent (phx-target={@myself}) run in the
component, not the LiveView, and are not seen by this hook.
Do Not Track
The socket can't see request headers, so the plug notes a DNT: 1 /
Sec-GPC: 1 visitor in the session and this hook reads it from there; such
a visitor's LiveView activity is not recorded either. That needs the plug to
run after :fetch_session, as it does in a standard :browser pipeline.
Summary
Functions
on_mount callback. Use :track_navigation.
Functions
@spec on_mount( :track_navigation, map() | :not_mounted_at_router, map(), Phoenix.LiveView.Socket.t() ) :: {:cont, Phoenix.LiveView.Socket.t()}
on_mount callback. Use :track_navigation.