@uniqueweb/shopify-analytics
v0.0.3
Published
Nuxt module — Shopify Analytics integration via Monorail
Downloads
82
Readme
@uniqueweb/shopify-analytics
Nuxt module for tracking Shopify analytics events via the Monorail API, inspired by Shopify's Hydrogen framework.
Features
- Page view tracking — automatic and manual page view events
- Product view tracking — track product impressions
- Collection view tracking — track collection page views
- Search tracking — track search queries
- Cart tracking — auto-tracking via
useCartAnalytics+ manual cart view events - Cookie management — manages
_shopify_y/_shopify_ssession cookies - Pub/Sub system — subscribe to any analytics event
- Monorail API — sends data to Shopify's analytics endpoint
Setup
npm install @uniqueweb/shopify-analyticsAdd the module to nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@uniqueweb/shopify-analytics'],
})Configuration
Environment Variables
NUXT_PUBLIC_SHOPIFY_ANALYTICS_DOMAIN=your-store.myshopify.com
NUXT_PUBLIC_SHOPIFY_ANALYTICS_SHOP_ID=gid://shopify/Shop/12345678
NUXT_PUBLIC_SHOPIFY_ANALYTICS_CHECKOUT_DOMAIN=checkout.your-store.com
NUXT_PUBLIC_SHOPIFY_ANALYTICS_HYDROGEN_SUBCHANNEL_ID=your-subchannel-id| Variable | Required | Description |
|---|---|---|
| NUXT_PUBLIC_SHOPIFY_ANALYTICS_DOMAIN | Yes | Shopify store domain (without https://), used for the Monorail endpoint |
| NUXT_PUBLIC_SHOPIFY_ANALYTICS_SHOP_ID | Yes | Shopify shop GID (gid://shopify/Shop/...) |
| NUXT_PUBLIC_SHOPIFY_ANALYTICS_CHECKOUT_DOMAIN | No | Checkout domain — used to calculate the common parent domain for cookie sharing between storefront and checkout |
| NUXT_PUBLIC_SHOPIFY_ANALYTICS_HYDROGEN_SUBCHANNEL_ID | No | Hydrogen subchannel ID |
Initialization
The module is split into three parts:
1. Plugin (automatic) — runs on app start, registers all Monorail event handlers via subscribe().
2. app.vue — called once after localization is available. Sets cookies and shop context via watchEffect so changes to consent or locale are reactive. Also wires up cart auto-tracking.
<!-- app.vue -->
<script setup lang="ts">
const shopifyShopStore = useShopifyShopStore()
const shopifyCartStore = useShopifyCartStore()
await shopifyShopStore.getLocalization()
if (import.meta.client) {
const { setShop } = useShopifyAnalytics()
const { public: { shopifyAnalytics } } = useRuntimeConfig()
watchEffect(() => {
// TODO: replace with reactive consent value from your CMP
const hasConsent = true
useShopifyCookies({
hasUserConsent: hasConsent,
checkoutDomain: shopifyAnalytics.checkoutDomain,
})
if (hasConsent) {
setShop({
shopId: shopifyAnalytics.shopId,
currency: shopifyShopStore.buyerCurrencyCode ?? 'EUR',
acceptedLanguage: shopifyShopStore.buyerLanguageCode ?? 'DE',
hydrogenSubchannelId: shopifyAnalytics.hydrogenSubchannelId,
})
}
else {
setShop(null)
}
})
useCartAnalytics(shopifyCartStore)
}
</script>
setShop()anduseShopifyCookies()are wrapped inwatchEffectso they react to consent changes and locale updates automatically.useCartAnalytics()is called once — it sets up an internalwatchon the cart store.
Usage
In all publish() calls, pass shop: getShop() instead of a hardcoded shop object:
<script setup lang="ts">
const { publish, AnalyticsEvent, getShop } = useShopifyAnalytics()
publish(AnalyticsEvent.PAGE_VIEWED, {
url: window.location.href,
shop: getShop(),
})
</script>Product View Tracking
<script setup lang="ts">
const { publish, AnalyticsEvent, getShop } = useShopifyAnalytics()
onMounted(() => {
publish(AnalyticsEvent.PRODUCT_VIEWED, {
url: window.location.href,
shop: getShop(),
products: [
{
id: product.value.id,
variantId: variant.value.id,
title: product.value.title,
variantTitle: variant.value.title,
vendor: product.value.vendor,
price: variant.value.price.amount,
quantity: 1,
},
],
})
})
</script>Collection View Tracking
<script setup lang="ts">
const { publish, AnalyticsEvent, getShop } = useShopifyAnalytics()
onMounted(() => {
publish(AnalyticsEvent.COLLECTION_VIEWED, {
url: window.location.href,
shop: getShop(),
collection: {
id: collection.value.id,
handle: collection.value.handle,
},
})
})
</script>Search Tracking
<script setup lang="ts">
const { publish, AnalyticsEvent, getShop } = useShopifyAnalytics()
function handleSearch(searchTerm: string) {
publish(AnalyticsEvent.SEARCH_VIEWED, {
url: window.location.href,
shop: getShop(),
searchTerm,
})
}
</script>Cart View Tracking
Publish manually when the cart is opened (e.g. in an aside/drawer component):
<script setup lang="ts">
const { publish, AnalyticsEvent, getShop } = useShopifyAnalytics()
const shopifyCartStore = useShopifyCartStore()
publish(AnalyticsEvent.CART_VIEWED, {
url: window.location.href,
shop: getShop(),
cart: shopifyCartStore.cart ?? null,
})
</script>Add to Cart / Remove from Cart Tracking
These events are tracked automatically by useCartAnalytics() (initialized in app.vue). It watches the cart store and compares lines between updates to detect additions and removals — including quantity changes.
No manual publish() call is needed for cart line events.
Custom Event Subscribers
// plugins/custom-analytics.client.ts
export default defineNuxtPlugin(() => {
const { subscribe, AnalyticsEvent } = useShopifyAnalytics()
subscribe(AnalyticsEvent.PRODUCT_VIEWED, (payload) => {
if (window.gtag) {
window.gtag('event', 'view_item', {
items: payload.products.map((p) => ({
item_id: p.id,
item_name: p.title,
price: p.price,
})),
})
}
})
subscribe(AnalyticsEvent.PRODUCT_ADD_TO_CART, (payload) => {
if (window.fbq) {
window.fbq('track', 'AddToCart', {
content_ids: [payload.currentLine.merchandise.id],
content_type: 'product',
})
}
})
})API Reference
useShopifyAnalytics()
publish<T>(event, payload)— Publish an analytics eventsubscribe<T>(event, callback)— Subscribe to an event (returns an unsubscribe function)setShop(shop | null)— Set or clear shop configuration (call inapp.vue)getShop()— Get current shop configuration (use inpublish()calls)canTrack()— Check if tracking is allowedAnalyticsEvent— Enum of all event names
All methods are no-ops on the server (SSR-safe).
useCartAnalytics(cartStore)
Watches a Pinia cart store for changes and automatically publishes PRODUCT_ADD_TO_CART / PRODUCT_REMOVED_FROM_CART events. Uses localStorage to deduplicate events across page reloads.
// Call once in app.vue, client-only
useCartAnalytics(shopifyCartStore)useShopifyCookies(options?)
Manages _shopify_y (1-year user token) and _shopify_s (30-min session token) cookies required by Shopify's Live View and analytics.
useShopifyCookies({
hasUserConsent: true, // removes cookies if false
domain: 'example.com', // optional, defaults to window.location.host
checkoutDomain: 'checkout.example.com', // optional, without https://
})checkoutDomain calculates the common parent domain between the storefront and the checkout subdomain and sets the cookie there, so it is accessible on both. Example:
| window.location.host | checkoutDomain | Cookie domain |
|---|---|---|
| store.com | checkout.store.com | .store.com |
| localhost:3000 | checkout.store.com | (no domain attr) |
Event Types
| Event | Trigger |
|---|---|
| AnalyticsEvent.PAGE_VIEWED | Manual — call on every page |
| AnalyticsEvent.PRODUCT_VIEWED | Manual — call on product page |
| AnalyticsEvent.COLLECTION_VIEWED | Manual — call on collection page |
| AnalyticsEvent.SEARCH_VIEWED | Manual — call on search |
| AnalyticsEvent.CART_VIEWED | Manual — call when cart opens |
| AnalyticsEvent.PRODUCT_ADD_TO_CART | Automatic via useCartAnalytics |
| AnalyticsEvent.PRODUCT_REMOVED_FROM_CART | Automatic via useCartAnalytics |
How It Works
- Plugin registers all Monorail event handlers via
subscribe()and marks the analytics system as ready useShopifyCookies()sets_shopify_y/_shopify_scookies, wrapped inwatchEffectfor consent reactivitysetShop()provides shop context (id, currency, language) used in every event payload, also insidewatchEffectuseCartAnalytics()watches the cart store; on each update it diffs the cart lines and publishes add/remove events- Pub/Sub queues events until the plugin is ready, then flushes automatically
- Monorail events are sent to
https://{domain}/.well-known/shopify/monorail/unstable/produce_batch
Local development
# Install dependencies
npm install
# Generate type stubs
npm run dev:prepare
# Develop with the playground
npm run dev
# Build the playground
npm run dev:build
# Run ESLint
npm run lint
# Release new version
npm run releaseCredits
Inspired by and based on the shopify-analytics module from pixelastronauts/nitrogen. Adapted and modified for this project — thank you for the inspiration!
License
MIT
