Skip to content

feat(analytics): set up GA4 + Tag Manager with conversion events#122

Merged
prudentbird merged 7 commits into
devfrom
feat/analytics-111-gtm-ga4
Jun 5, 2026
Merged

feat(analytics): set up GA4 + Tag Manager with conversion events#122
prudentbird merged 7 commits into
devfrom
feat/analytics-111-gtm-ga4

Conversation

@prudentbird

Copy link
Copy Markdown
Member

Summary

Brings analytics to the marketing surface via Google Tag Manager (GA4's delivery vehicle), tracks key conversions, and adds a Search Console verification hook. All of it stays inert until IDs are provisioned (#113), so merging is safe.

  • GTM loader (GoogleTagManager) — next/script afterInteractive (non-blocking) + <noscript> iframe fallback. Renders only when NEXT_PUBLIC_GTM_ID is set.
  • Search Console — verification meta via the Metadata API when GOOGLE_SITE_VERIFICATION is set.
  • Conversion events via a sendGTMEvent dataLayer helper:
    • cta_click — hero + CTA-section primary buttons (sign-up handoff to /register), tagged with cta_location.
    • contact_click — footer + contact-page email links.
    • engaged — meaningful engagement, fired once after 30s or 50% scroll on the home page.
  • New env: NEXT_PUBLIC_GTM_ID, GOOGLE_SITE_VERIFICATION (both optional).

Done when (issue #111)

  • Analytics (GA4 via Tag Manager) live on every public page without blocking page load.
  • Key conversion actions tracked as events (CTA clicks, sign-up/handoff, contact, meaningful engagement).
  • A Search Console verification hook is in place.

Validation

  • npm run typecheck ✅ / npm run build
  • With a dummy GTM-TEST123 + verification token: confirmed the GTM script, the <noscript> iframe, and the google-site-verification meta all render. With no env set: confirmed fully inert.

Notes

Final PR in the SEO stack (targets feat/geo-110-llms-txt). Real GTM/GA4 IDs and the verification token are provisioned in ops issue #113. Project already uses Vercel Analytics + Mixpanel; this adds GA4/GTM alongside them per the issue.

Closes #111

Every public page now emits Open Graph and Twitter card tags (title,
description, url, image) so links render branded previews on LinkedIn,
Slack, WhatsApp and X.

- Add a branded 1200x630 social card generated with next/og, served
  site-wide via app/opengraph-image and reused for app/twitter-image.
- Add buildMetadata() helper that produces matching title/description/
  canonical plus OG and Twitter card objects for each page.
- Route home and auth pages through the helper; add OG/Twitter defaults
  to the root layout for inheritance.

Refs #106
Footer links now resolve to real pages or anchors instead of placeholders
that fell back to the homepage, and the previously missing contact and
legal pages exist.

- Footer: grouped Product/Company/Legal navigation with a real contact
  email, on-page anchors (#how-it-works, #faq), the parent company site,
  and links to the new pages.
- Add /contact (indexable) with a real, monitored address.
- Add /privacy and /terms minimal legal pages (noindex,follow).
- Add SitePage shell for consistent standalone-page layout.

Refs #112
Serve a robots.txt that allows public pages, blocks the API and gated
admin/user areas, and points crawlers to the sitemap.

Refs #107
Serve a sitemap.xml listing the public, indexable pages with last-modified,
change frequency, and priority. The page list lives in a central
publicRoutes constant so adding a public page updates the sitemap from one
place; gated, admin, and API routes are excluded.

Refs #108
Emit JSON-LD on every page so search and AI answer engines can identify
the product, the organization behind it, and its offering.

- Site-wide @graph (Organization, WebSite, SoftwareApplication) rendered
  from the root layout.
- Organization mirrors Ajared coverage: legal name, two locations
  (Toronto, Abuja) with addresses and geo, contact point, and sameAs.
- SoftwareApplication describes Retailytics with category, offer (free
  trial), and a feature list.
- FAQPage emitted on the home page from the existing FAQ content.
- BreadcrumbList on standalone pages via SitePage.

Refs #109
Serve an llms.txt manifest at the site root so AI crawlers know what
Retailytics is and which pages are authoritative. Includes a product
summary, the core pages, a company description, and key features,
generated from the shared site config to stay in sync.

Refs #110
Wire Google Tag Manager (the GA4 delivery vehicle) into every page
without blocking page load, track key conversion actions, and add a
Search Console verification hook. Everything stays inert until the IDs
are provisioned (see #113).

- GoogleTagManager: afterInteractive GTM loader + noscript fallback,
  rendered only when NEXT_PUBLIC_GTM_ID is set.
- Search Console verification via metadata when GOOGLE_SITE_VERIFICATION
  is set.
- sendGTMEvent dataLayer helper, plus event instrumentation:
  - cta_click on the hero and CTA-section primary buttons (sign-up
    handoff to /register), with cta_location.
  - contact_click on the footer and contact-page email links.
  - engaged (meaningful engagement) after 30s or 50% scroll on the home
    page.

Refs #111
@claude

claude Bot commented Jun 5, 2026

Copy link
Copy Markdown

Claude finished @prudentbird's task —— View job


PR Review: GA4 + Tag Manager Analytics Implementation

Review Checklist

  • Read repository CLAUDE.md for setup instructions and guidelines
  • Review all 12 changed files for code quality and implementation
  • Analyze security implications of analytics integration
  • Check performance considerations and best practices
  • Assess test coverage needs and recommendations
  • Provide comprehensive review feedback via GitHub comment

✅ Overall Assessment

Excellent implementation that follows Next.js and React best practices. The code is well-structured, type-safe, and follows a clean separation of concerns. The analytics integration is properly implemented with good security practices.

🔍 Detailed Review

🏗️ Architecture & Code Quality

Score: 9/10

  • Clean Component Structure: All analytics components are properly isolated in src/components/analytics/
  • Type Safety: Excellent TypeScript usage with proper type definitions (GTMEventParams)
  • Separation of Concerns: Clear separation between GTM loader, event tracking, and UI components
  • Reusable Components: TrackedCta and TrackedMailto are well-designed wrapper components

🔒 Security Considerations

Score: 10/10

  • Environment Variable Validation: Proper use of Zod validation in env.ts:13,18
  • Conditional Rendering: GTM components only render when NEXT_PUBLIC_GTM_ID is set (google-tag-manager.tsx:9,23)
  • No Hardcoded Values: All sensitive data properly externalized to environment variables
  • XSS Prevention: Template literals are properly escaped in GTM script injection
  • Server-side Variables: GOOGLE_SITE_VERIFICATION correctly marked as server-only

Performance Considerations

Score: 9/10

Excellent:

  • Non-blocking Load: Uses strategy="afterInteractive" for GTM script (google-tag-manager.tsx:12)
  • Passive Event Listeners: Scroll listener properly marked as passive (engagement-tracker.tsx:28)
  • Cleanup Logic: Proper event listener cleanup in useEffect return function
  • Efficient Tracking: Single-fire engagement tracking with ref-based deduplication

Minor Optimization Opportunity:

  • Consider debouncing the scroll event in EngagementTracker to reduce frequency:
// In engagement-tracker.tsx:21-24, consider:
const onScroll = debounce(() => {
  const scrolled = window.scrollY / (document.body.scrollHeight - window.innerHeight || 1);
  if (scrolled >= ENGAGEMENT_SCROLL_RATIO) fire('scroll');
}, 100);

🧪 Test Coverage Needs

Score: 6/10

Missing Test Cases:

  1. GTM Component Tests: Mock environment variables and verify conditional rendering
  2. Analytics Library Tests: Test sendGTMEvent function with mocked window.dataLayer
  3. Engagement Tracker Tests: Mock scroll events and timers
  4. Integration Tests: Verify event firing on CTA clicks and email interactions

Suggested Test Structure:

// __tests__/analytics/google-tag-manager.test.tsx
describe('GoogleTagManager', () => {
  it('renders script when GTM_ID is set', () => {})
  it('returns null when GTM_ID is not set', () => {})
})

📱 Browser Compatibility

Score: 10/10

  • SSR Safe: Proper typeof window === 'undefined' checks
  • Progressive Enhancement: Graceful degradation with <noscript> fallback
  • Modern API Usage: Uses standard Web APIs appropriately

🔧 Implementation Details

Strengths:

  • Event Taxonomy: Well-structured event naming (cta_click, contact_click, engaged)
  • Contextual Parameters: Proper use of cta_location and contact_location for attribution
  • Single Responsibility: Each component has a clear, focused purpose

Minor Suggestions:

  1. Error Handling: Consider adding try-catch in sendGTMEvent for robustness:

    export function sendGTMEvent(event: string, params: GTMEventParams = {}): void {
      if (typeof window === 'undefined') return;
      try {
        window.dataLayer = window.dataLayer ?? [];
        window.dataLayer.push({ event, ...params });
      } catch (error) {
        console.warn('GTM event failed:', error);
      }
    }
  2. Documentation: Add JSDoc comments for public functions and component props

📋 Action Items

Required Changes: ❌ None

This PR is ready to merge as-is.

Recommended Improvements (Future PRs):

  1. Add comprehensive test suite for analytics components
  2. Consider scroll event debouncing for performance optimization
  3. Add error handling to sendGTMEvent function
  4. Add JSDoc documentation

🚀 Deployment Readiness

Safe to merge - The implementation properly handles missing environment variables and won't break in any environment.

Production Ready - Follows Google's recommended GTM implementation patterns with proper fallbacks.


Final Verdict: LGTM 🎉

This is a high-quality implementation that demonstrates excellent React/Next.js practices. The analytics integration is secure, performant, and maintainable.

@prudentbird prudentbird changed the base branch from feat/geo-110-llms-txt to dev June 5, 2026 19:49
@prudentbird prudentbird merged commit 959ba0a into dev Jun 5, 2026
1 check passed
@prudentbird prudentbird deleted the feat/analytics-111-gtm-ga4 branch June 5, 2026 19:52
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.

feat(analytics): set up GA4 + Tag Manager with conversion events

1 participant