Skip to content

feat(template): first-party Shopify analytics#414

Open
blurrah wants to merge 4 commits into
mainfrom
shopify-first-party-analytics
Open

feat(template): first-party Shopify analytics#414
blurrah wants to merge 4 commits into
mainfrom
shopify-first-party-analytics

Conversation

@blurrah

@blurrah blurrah commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Wires Shopify's first-party analytics (from the @shopify/hydrogen preview package the template already uses for its storefront client) into the storefront. No new env vars; a clone without store env builds unchanged.

  • lib/analytics/client.ts — singleton around createStorefrontAnalytics with typed publish helpers and syncCartAnalytics (domain CartAnalyticsCart); pre-init publishes are queued
  • components/analytics.tsxShopifyAnalytics server component (Suspense-wrapped) resolving shop id + passing store domain and the public Storefront token to the client
  • components/analytics-client.tsx — initializer (consent, page_viewed on route change, cart sync from cart context) + view trackers mounted on PDP, collection, search, and cart pages
  • Data plumbing: CART_FRAGMENT gains updatedAt/sku/vendor, COLLECTION_FIELDS_FRAGMENT gains id, new cached getShopId() operation — all schema-validated
  • Cart add/remove events derive from the tracker's updatedAt diffing, so optimistic cart updates are skipped automatically; no mutation call sites touched
  • Rollout log entry shopify-first-party-analytics + enable-analytics skill note (docs synced via sync-skills)

Build passes with all routes keeping PPR. Not verified against a live store: consent script load and Monorail requests (validation steps in the rollout entry). Known quirk: cart_viewed on a direct /cart landing fires before the first cart sync and reports a null cart.

@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shop-docs Ready Ready Preview, Comment Jul 9, 2026 10:16am
shop-template Ready Ready Preview, Comment, Open in v0 Jul 9, 2026 10:16am

Comment thread apps/template/lib/analytics/client.ts
…ound (memory leak) when the analytics bus never initializes for a session.

This commit fixes the issue reported at apps/template/lib/analytics/client.ts:31

## Bug

In `apps/template/lib/analytics/client.ts`, `withBus()` pushes a publish closure onto the module-level `queue` whenever `bus` is `null`. The `queue` is only ever drained inside `initAnalytics()`.

`initAnalytics()` is called exclusively from `ShopifyAnalyticsClient` (`components/analytics-client.tsx`), which is only mounted when the server component `ShopifyAnalyticsLoader` (`components/analytics.tsx`) successfully resolves a `shopId`:

```ts
const shopId = await withFallback(getShopId(), undefined);
if (!shopId) return null;
```

`withFallback` swallows any error from `getShopId()` and returns `undefined`. So a transient Shopify failure (or missing env vars) makes the loader return `null`, `ShopifyAnalyticsClient` never mounts, and `initAnalytics()` is never called.

### Concrete trigger
1. First render of the layout-level `ShopifyAnalyticsLoader` hits a transient `getShopId()` failure → returns `null`.
2. Because it is a layout-level App Router server component, it is **not** re-rendered on client-side navigations for the rest of the session → `bus` stays `null` permanently.
3. The per-page trackers (`ProductViewTracker`, `CollectionViewTracker`, `SearchViewTracker`, `CartViewTracker`, plus `trackPageView`) run unconditionally on every navigation, each calling `withBus()` → each pushes onto `queue`.

With no bus to drain it, `queue` grows by (at least) one closure per navigation for the entire session — an unbounded memory leak — while every analytics event is silently lost.

## Fix

Added a `MAX_QUEUE_LENGTH` cap (100) and made `withBus()` drop the oldest pending publish (`queue.shift()`) before pushing when the queue is full. This bounds memory in the never-initializes case while preserving the normal pre-init drain behavior (the queue is realistically tiny before init in the happy path). The dropped-event behavior is no worse than today (events are already lost when the bus never comes), but memory no longer leaks.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: blurrah <borisbesemer@gmail.com>
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