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 ManagerThere is no Google Tag Manager container (
GTM-XXXXXXX) anywhere in this codebase. The site loads GA4 directly viagtag.js, injected client-side byGoogleAnalytics.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:
| Brand | Domain | GA4 web stream URL | Env var |
|---|---|---|---|
| HiveJournal | hivejournal.com | https://hivejournal.com | NEXT_PUBLIC_GA_MEASUREMENT_ID_HIVEJOURNAL |
| Graphene | graphene.fm | https://graphene.fm | NEXT_PUBLIC_GA_MEASUREMENT_ID_GRAPHENE |
| write.cafe | write.cafe | https://write.cafe | NEXT_PUBLIC_GA_MEASUREMENT_ID_WRITECAFE |
| Lovio | lovio.io | https://lovio.io | NEXT_PUBLIC_GA_MEASUREMENT_ID_LOVIO |
| DreamPro | dreampro.io | https://dreampro.io | NEXT_PUBLIC_GA_MEASUREMENT_ID_DREAMPRO |
| (fallback) | any other host / staging | — | NEXT_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:
- Go to Google Analytics → Admin → Create property.
- Name it after the brand (e.g. "Graphene"), set timezone/currency.
- Under the new property, Create a Web data stream pointing at that brand's domain.
- 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
- Scope each var to the
Productionenvironment. Vercel scopes vars per environment; a var set only for Preview won't exist in the live build.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"):
| Event | Fires when | Mark as key event in |
|---|---|---|
sign_up | account created | all brands |
sign_up_started | signup CTA clicked | all (funnel top) |
purchase | subscription / payment | all brands |
subscribe_cta_click | subscribe CTA clicked | Graphene, HiveJournal |
first_entry | user's first journal entry | HiveJournal, write.cafe |
entry_created | any journal entry | HiveJournal, write.cafe |
day_7_active | active on day 7 (retention) | HiveJournal |
workout_checkin | workout window check-in | DreamPro |
dream_created / action_created | dream / action note created | DreamPro, HiveJournal |
feature_usage | generic 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
- Redeploy after setting the vars (see gotcha #2 above).
- Open each live domain, then in the browser console run:
It returnswindow.__gaDebug{ brand, gaId, perBrandIdInlined }:perBrandIdInlined: "G-…"→ the brand var was inlined; traffic goes to that property. ✅perBrandIdInlined: nullbutgaId: "G-…"→ the brand var wasn't present at build; traffic is going to the fallback property. Set the brand var + redeploy.gaId: undefined(orwindow.__gaDebugis undefined) → nothing inlined — not even the fallback. SetNEXT_PUBLIC_GA_MEASUREMENT_ID+ redeploy.
- 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 eventtrackSignUp(method)- Track user sign up (email/google/spotify)trackLogin(method)- Track user logintrackEntryCreated(notebookId)- Track journal entry creationtrackWorkoutCheckIn(activityType)- Track workout window check-intrackDreamCreated(category)- Track dream creationtrackAIBreakdownGenerated(dreamId, stepCount)- Track AI breakdown generationtrackDropSent(dropType)- Track encouragement drop senttrackActionCreated(category, fromDreamStep)- Track action note creationtrackPurchase(amount, currency)- Track subscription/paymenttrackFeatureUsage(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
- The
GoogleAnalyticscomponent loads the GA4 script using Next.jsScriptcomponent - It initializes
gtagand configures it with your Measurement ID - It automatically tracks page views on route changes using Next.js App Router hooks
- 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
- Set
NEXT_PUBLIC_GA_MEASUREMENT_IDin.env.local - Run
npm run dev - Open browser DevTools → Network tab
- Look for requests to
www.googletagmanager.com - Check browser console for any GA-related errors
Production Verification
- Visit your production site
- Open Google Analytics → Realtime reports
- You should see your visit appear
- 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:
- You're checking the wrong tool. Data is in GA4 (
analytics.google.com), not Google Tag Manager (tagmanager.google.com). GTM is not installed. - You're looking at the wrong GA4 property. Run
window.__gaDebugon the live domain (see §4 Verify installation) to see whichgaIdis actually receiving the traffic, then open that property's Realtime. - The var wasn't inlined.
perBrandIdInlined: nullorgaId: undefinedmeans 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
gtagis 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
useEffecthook - Ensure
usePathnameanduseSearchParamsare 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