feat(template): first-party Shopify analytics#414
Open
blurrah wants to merge 4 commits into
Open
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…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>
…ctEvent in analytics trackers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wires Shopify's first-party analytics (from the
@shopify/hydrogenpreview 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 aroundcreateStorefrontAnalyticswith typed publish helpers andsyncCartAnalytics(domainCart→AnalyticsCart); pre-init publishes are queuedcomponents/analytics.tsx—ShopifyAnalyticsserver component (Suspense-wrapped) resolving shop id + passing store domain and the public Storefront token to the clientcomponents/analytics-client.tsx— initializer (consent,page_viewedon route change, cart sync from cart context) + view trackers mounted on PDP, collection, search, and cart pagesCART_FRAGMENTgainsupdatedAt/sku/vendor,COLLECTION_FIELDS_FRAGMENTgainsid, new cachedgetShopId()operation — all schema-validatedupdatedAtdiffing, so optimistic cart updates are skipped automatically; no mutation call sites touchedshopify-first-party-analytics+enable-analyticsskill 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_viewedon a direct/cartlanding fires before the first cart sync and reports a null cart.