Skip to content

feat: pin the GA4 client_id to the device - #31

Merged
vpetersson-bot merged 1 commit into
masterfrom
feat/pin-ga-client-id
Aug 14, 2026
Merged

feat: pin the GA4 client_id to the device#31
vpetersson-bot merged 1 commit into
masterfrom
feat/pin-ga-client-id

Conversation

@vpetersson-bot

Copy link
Copy Markdown
Contributor

Pins the GA4 client_id to the Screenly device id, so one screen is one GA4 user.

GA4's own client_id lives in the _ga cookie, and these players largely boot with fresh storage, so it churns and does not identify a screen. Where a player sends asset metadata there is now a stable device id, and the kit derives a client_id from the same hash that produces player_device, so the two always agree about which screen this is.

Why the tag moves into the kit bootstrap

The inline <head> snippet is now analyticsBootstrap() from the kit, because three constraints rule out the easier options:

  • config has to wait for the device id. client_id is stamped onto each event as it is sent, so the bootstrap has to own the config call rather than patch it afterwards.
  • It cannot move into main.js. That is a separate request, and a share of screens fire a page_view and never get further. Moving config there would turn a counting fix into a data loss for exactly those screens.
  • It cannot be rendered into this HTML. The page is edge-cached with no per-screen component, so a baked-in client_id would be handed to every screen that hit the cache afterwards and the fleet would collapse onto one GA4 user. Hence fetching the no-store /api/player route at runtime.

Fails open

The profile arriving, the fetch rejecting, a non-ok response, or a timeout all end in exactly one config call. An offline screen still reports, just with GA4's default behaviour. Telemetry is never the reason a screen goes quiet.

Kit release: https://github.com/Screenly-Labs/signage-kit/releases/tag/2026.8.8

🤖 Generated with Claude Code

GA4's own client_id lives in the _ga cookie and these players largely boot with
fresh storage, so it churns and does not identify a screen. Where a Screenly
player sends asset metadata there is now a stable device id, and signage-kit
derives a client_id from the same hash that produces player_device, so the two
always agree about which screen this is.

The inline <head> tag becomes analyticsBootstrap() from the kit. It has to own
the config call, because client_id is stamped onto each event as it is sent; it
cannot move into main.js, because screens that never load the bundle would then
report nothing at all; and it cannot be rendered into this HTML, because the page
is edge-cached with no per-screen component and every screen would inherit
whichever one warmed the cache. So it fetches the no-store /api/player route at
runtime.

Fails open: profile arrival, fetch rejection, a non-ok response and a timeout all
end in exactly one config call, so an offline screen still reports with GA4's
default behaviour.

Also bumps signage-kit to 2026.8.8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vpetersson-bot
vpetersson-bot merged commit 9c47626 into master Aug 14, 2026
1 check passed
@vpetersson-bot
vpetersson-bot deleted the feat/pin-ga-client-id branch August 14, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant