setup-guides

Google Analytics Setup Guide

This guide explains how to set up and use Google Analytics (GA4) in HiveJournal.

⚠️ This is GA4 (gtag.js), NOT Google Tag Manager

There is no Google Tag Manager container (GTM-XXXXXXX) anywhere in this codebase. The site loads GA4 directly via gtag.js, injected client-side by GoogleAnalytics.tsx.

  • Track data at analytics.google.com (GA4 → Realtime / Reports).
  • A tagmanager.google.com (GTM) dashboard will always show 0 — there is nothing on the site feeding it. Don't use GTM to verify; it's not installed.

What to create (one GA4 property per brand)

The same Vercel deployment serves five brand domains, and GoogleAnalytics.tsx routes each one to its own GA4 property by hostname (via detectBrandSlug()) — the same per-brand isolation the Meta pixel does in FacebookPixel.tsx. This keeps each brand's conversions out of the others' data, which matters most for Graphene: its conversion events feed Google Ads optimization (see GOOGLE_ADS_CONVERSION_IMPORT.md), and a property diluted with HiveJournal/WriteCafe traffic would muddy the conversion rate and pollute remarketing audiences.

Create one GA4 property (with one Web data stream) for each domain below, then map its Measurement ID to the matching env var:

BrandDomainGA4 web stream URLEnv var
HiveJournalhivejournal.comhttps://hivejournal.comNEXT_PUBLIC_GA_MEASUREMENT_ID_HIVEJOURNAL
Graphenegraphene.fmhttps://graphene.fmNEXT_PUBLIC_GA_MEASUREMENT_ID_GRAPHENE
write.cafewrite.cafehttps://write.cafeNEXT_PUBLIC_GA_MEASUREMENT_ID_WRITECAFE
Loviolovio.iohttps://lovio.ioNEXT_PUBLIC_GA_MEASUREMENT_ID_LOVIO
DreamProdreampro.iohttps://dreampro.ioNEXT_PUBLIC_GA_MEASUREMENT_ID_DREAMPRO
(fallback)any other host / stagingNEXT_PUBLIC_GA_MEASUREMENT_ID

The fallback is mandatory. Any host without its own brand var (staging, preview deploys, a domain you haven't made a property for yet) falls back to NEXT_PUBLIC_GA_MEASUREMENT_ID. If that is also unset, nothing is inlined and the brand reports 0 traffic — this is the #1 cause of "0 traffic on every domain". Point the fallback at a catch-all property (or simply reuse the HiveJournal property) so traffic is never silently dropped.

You can roll brands out one at a time — set only the brand vars whose properties you've created; the rest keep using the fallback until you're ready.

Setup

1. Create the GA4 properties

For each row in the table above:

  1. Go to Google AnalyticsAdminCreate property.
  2. Name it after the brand (e.g. "Graphene"), set timezone/currency.
  3. Under the new property, Create a Web data stream pointing at that brand's domain.
  4. Copy the stream's Measurement ID (format G-XXXXXXXXXX).

2. Add the environment variables

Set these in Vercel → Project → Settings → Environment Variables (and in .env.local for local dev). Set whichever brand IDs you've created:

NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX             # fallback — REQUIRED
NEXT_PUBLIC_GA_MEASUREMENT_ID_HIVEJOURNAL=G-XXXXXXXXXX # hivejournal.com
NEXT_PUBLIC_GA_MEASUREMENT_ID_GRAPHENE=G-XXXXXXXXXX    # graphene.fm
NEXT_PUBLIC_GA_MEASUREMENT_ID_WRITECAFE=G-XXXXXXXXXX   # write.cafe
NEXT_PUBLIC_GA_MEASUREMENT_ID_LOVIO=G-XXXXXXXXXX       # lovio.io
NEXT_PUBLIC_GA_MEASUREMENT_ID_DREAMPRO=G-XXXXXXXXXX    # dreampro.io

🔑 Two gotchas that cause "0 traffic" even when the IDs look right

  1. Scope each var to the Production environment. Vercel scopes vars per environment; a var set only for Preview won't exist in the live build.
  2. NEXT_PUBLIC_* is inlined at BUILD time, not read at runtime. Saving a var in Vercel does nothing to the already-live bundle — you must redeploy (Vercel → Deployments → ⋯ → Redeploy, with build cache unchecked) after adding or changing any of these. There is no runtime fallback path; an ID that wasn't present at build time simply isn't there.

For Railway (backend — only needed if you run the frontend there): same vars, under Settings → Variables, then redeploy.

3. Mark key events as conversions

The app already fires these GA4 events (from analytics.ts via gtag('event', …)). After data starts flowing, mark the relevant ones as Key events in each property (Admin → Events → toggle "Mark as key event"):

EventFires whenMark as key event in
sign_upaccount createdall brands
sign_up_startedsignup CTA clickedall (funnel top)
purchasesubscription / paymentall brands
subscribe_cta_clicksubscribe CTA clickedGraphene, HiveJournal
first_entryuser's first journal entryHiveJournal, write.cafe
entry_createdany journal entryHiveJournal, write.cafe
day_7_activeactive on day 7 (retention)HiveJournal
workout_checkinworkout window check-inDreamPro
dream_created / action_createddream / action note createdDreamPro, HiveJournal
feature_usagegeneric feature event (see feature_name param)as needed

For Graphene specifically, the purchase / subscribe_cta_click key events are what you import into Google Ads as conversions — see GOOGLE_ADS_CONVERSION_IMPORT.md.

4. Verify installation

  1. Redeploy after setting the vars (see gotcha #2 above).
  2. Open each live domain, then in the browser console run:
    window.__gaDebug
    
    It returns { brand, gaId, perBrandIdInlined }:
    • perBrandIdInlined: "G-…" → the brand var was inlined; traffic goes to that property. ✅
    • perBrandIdInlined: null but gaId: "G-…" → the brand var wasn't present at build; traffic is going to the fallback property. Set the brand var + redeploy.
    • gaId: undefined (or window.__gaDebug is undefined) → nothing inlined — not even the fallback. Set NEXT_PUBLIC_GA_MEASUREMENT_ID + redeploy.
  3. In GA4 → Realtime for the matching property, confirm your visit appears within a few seconds.

Features

Automatic Page View Tracking

The Google Analytics component automatically tracks:

  • Initial page load
  • Route changes (using Next.js App Router)
  • Page paths with query parameters

Custom Event Tracking

The analytics utility (src/lib/analytics.ts) provides helper functions for tracking custom events:

Available Functions

  • trackEvent(eventName, eventParams) - Track any custom event
  • trackSignUp(method) - Track user sign up (email/google/spotify)
  • trackLogin(method) - Track user login
  • trackEntryCreated(notebookId) - Track journal entry creation
  • trackWorkoutCheckIn(activityType) - Track workout window check-in
  • trackDreamCreated(category) - Track dream creation
  • trackAIBreakdownGenerated(dreamId, stepCount) - Track AI breakdown generation
  • trackDropSent(dropType) - Track encouragement drop sent
  • trackActionCreated(category, fromDreamStep) - Track action note creation
  • trackPurchase(amount, currency) - Track subscription/payment
  • trackFeatureUsage(featureName, metadata) - Track feature usage

Usage Example

import { trackEvent, trackSignUp } from '@/lib/analytics'

// Track a custom event
trackEvent('button_click', {
  button_name: 'signup',
  location: 'homepage',
})

// Track user sign up
trackSignUp('email')

Implementation Details

Component Location

  • Component: apps/frontend/src/components/analytics/GoogleAnalytics.tsx
  • Utilities: apps/frontend/src/lib/analytics.ts
  • Integration: apps/frontend/src/app/layout.tsx

How It Works

  1. The GoogleAnalytics component loads the GA4 script using Next.js Script component
  2. It initializes gtag and configures it with your Measurement ID
  3. It automatically tracks page views on route changes using Next.js App Router hooks
  4. Custom events can be tracked using the utility functions

Privacy Considerations

  • Google Analytics respects user privacy settings
  • Consider adding a cookie consent banner if required by your jurisdiction
  • You can configure data retention and privacy settings in Google Analytics dashboard

Testing

Local Development

  1. Set NEXT_PUBLIC_GA_MEASUREMENT_ID in .env.local
  2. Run npm run dev
  3. Open browser DevTools → Network tab
  4. Look for requests to www.googletagmanager.com
  5. Check browser console for any GA-related errors

Production Verification

  1. Visit your production site
  2. Open Google Analytics → Realtime reports
  3. You should see your visit appear
  4. Navigate between pages and verify page views are tracked

Troubleshooting

"0 traffic" on a brand (most common)

Work through these in order — the first three cover ~all real cases:

  1. You're checking the wrong tool. Data is in GA4 (analytics.google.com), not Google Tag Manager (tagmanager.google.com). GTM is not installed.
  2. You're looking at the wrong GA4 property. Run window.__gaDebug on the live domain (see §4 Verify installation) to see which gaId is actually receiving the traffic, then open that property's Realtime.
  3. The var wasn't inlined. perBrandIdInlined: null or gaId: undefined means the env var wasn't present at build time — set it (Production scope) and redeploy with build cache off. Saving the var without redeploying does nothing.

GA Not Loading

  • Check that NEXT_PUBLIC_GA_MEASUREMENT_ID (or the brand var) is set correctly
  • Verify the Measurement ID format (should start with G-)
  • Check browser console for errors
  • Ensure ad blockers aren't blocking GA scripts

Events Not Tracking

  • Verify gtag is available: console.log(window.gtag)
  • Check that event tracking functions are being called
  • Use Google Analytics DebugView for real-time event debugging

Page Views Not Tracking

  • Verify the component is included in the root layout
  • Check that route changes are triggering the useEffect hook
  • Ensure usePathname and useSearchParams are working correctly

Next Steps

Consider adding:

  • Enhanced ecommerce tracking (for subscriptions)
  • User property tracking (user ID, subscription status)
  • Custom dimensions (user type, feature usage)
  • Conversion goals in Google Analytics dashboard
GOOGLE ANALYTICS SETUP — Docs | HiveJournal