Analytics Stack Overview

Three analytics products run on all sv-website pages. Each serves a distinct purpose.

PlatformCostPrimary UseRetention
Google Analytics 4 (GA4)FreeBehavioral analytics, acquisition channels, UTM attribution, engagement time, conversion funnels14 months (default)
Vercel Web Analytics$10/month (Plus tier)Custom event tracking, privacy-friendly visitor counts, referrer breakdown24 months
Vercel Speed InsightsIncluded with ProCore Web Vitals (LCP, FID, CLS), page load performanceRolling

When to Use Which

QuestionUse
How many visitors this week?Either (Vercel is simpler)
Which pages get the most views?Either
Where is traffic coming from? (referrers, UTM campaigns)GA4 (more detail)
How long do visitors spend on the page?GA4
What % of visitors scroll to the signup form?GA4 (scroll events)
Are partnership emails driving traffic?GA4 (UTM breakdown)
Custom event tracking (signup_submit, cta_click)?Vercel Analytics (already wired)
Core Web Vitals / page speed?Vercel Speed Insights

Google Analytics 4 (GA4)

Added Feb 28, 2026. Provides deeper behavioral analytics beyond what Vercel Web Analytics captures — user flows, engagement time, acquisition channels, and conversion events.

Configuration

ItemValue
AccountMy Storage Valet LLC
Propertymystoragevalet.com
Property ID526241313
Measurement IDG-9SBYJ8QEC1
Stream ID13680726133
Stream NameStorage Valet Website
CostFree (included with Google account)
PagesAll 9 sv-website pages (8 main + promo template)
Dashboardanalytics.google.com (sign in with zach@mystoragevalet.com)
TimezoneEastern (US)
IndustryBusiness & Industrial
Data Collection StartFeb 28, 2026 (no retroactive data)

Conditional Loading (Production Only)

GA4 only fires on the production domain. Vercel preview deploys, localhost, and any other hostname are excluded. This prevents development and AI agent traffic on non-production URLs from polluting analytics data. Implemented Mar 21, 2026.

<!-- Google Analytics (GA4) — only loads on production domain -->
<script>
  if (location.hostname === 'www.mystoragevalet.com' || location.hostname === 'mystoragevalet.com') {
    var s = document.createElement('script');
    s.async = true;
    s.src = 'https://www.googletagmanager.com/gtag/js?id=G-9SBYJ8QEC1';
    document.head.appendChild(s);
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-9SBYJ8QEC1');
  }
</script>

This snippet appears identically in all 9 HTML files (8 main pages + promo/index.html). The dataLayer.push pattern queues events before gtag.js loads, so dynamic script creation works identically to a static <script async> tag.

Enhanced Measurement (Auto-Tracked)

These events are captured automatically by GA4 with no additional code:

EventWhat It Captures
Page viewsEvery page load and client-side navigation
ScrollsWhen user scrolls past 90% of page
Outbound clicksClicks to external domains (e.g., portal.mystoragevalet.com, Stripe checkout)
Site searchQuery parameters in URL (not currently used)
File downloadsClicks on document/file links

Still To Build Out

Internal Traffic Filtering

GA4 filters out traffic from Zach's home network to prevent internal browsing from inflating analytics data. This is a two-part system: a traffic rule that tags matching IPs, and a data filter that excludes tagged events.

GA4 data filters are permanent

Once a data filter is set to Active, excluded data is never processed and can never be recovered — not in reports, not in BigQuery exports. Filters only apply forward; historical data is unaffected.

Internal Traffic Rule

ItemValue
Rule Name1200 Clinton St (ZB's Home Network)
traffic_type Valueinternal
IPv471.187.246.67/32 (CIDR, exact match)
IPv62600:4041:41ff:a200::/64 (CIDR, subnet)
ISPOptimum/Cablevision (residential)
LocationGA4 Admin > Data Streams > Configure Tag Settings > Define Internal Traffic

Data Filter

ItemValue
Filter NameInternal Traffic
Filter TypeInternal Traffic
OperationExclude
Parametertraffic_type exactly matches internal
StateActive
LocationGA4 Admin > Data collection and modification > Data filters

Coverage

ScenarioFiltered?Why
Any device on home Wi-Fi (Mac Studio, MacBook Air, phone)YesSame public IP
Claude in Chrome from Zach's machineYesSame public IP
Phone on cellular dataNoCarrier assigns rotating IPs
MacBook Air on other Wi-Fi (coffee shop, etc.)NoDifferent public IP
AI cloud agents (NotebookLM, Perplexity, ChatGPT)NoCloud server IPs, rotating and unpredictable
Traditional bots (Googlebot, Bingbot)N/AGA4 auto-excludes known bots via IAB list

Updating the IP Address

The ISP may rotate the public IP occasionally. If analytics start showing internal traffic again:

  1. Check current IPv4 at api.ipify.org
  2. Go to GA4 Admin > Data Streams > Storage Valet Website > Configure Tag Settings > Show More > Define Internal Traffic
  3. Edit the “1200 Clinton St” rule and update the IPv4 value
  4. For IPv6, search “what is my IP” in a browser to see the current IPv6 address and verify it still falls within the /64 subnet

Known Unfiltered Noise Sources

These appear in GA4 data and cannot be practically filtered. Volume is low (~20–30 sessions/week combined).

SourceTypical City in GA4What It Is
NotebookLMVariousGoogle AI tool analyzing the site; shows as notebooklm.google.com referrer
Microsoft Azure botsMoses Lake, WAAI agents running on Azure data center infrastructure
Google/tech infrastructureSan Jose, CAVarious Google services and crawlers
Other cloud botsChicago, IL; (not set)Unidentified automated traffic from cloud data centers

Automatic Bot Exclusion

GA4 automatically excludes traffic from known bots and spiders using Google's own research and the IAB (Interactive Advertising Bureau) International Spiders and Bots List. This is always on and cannot be disabled or configured. It does not cover AI browsing agents that use real browser instances.

Vercel Web Analytics

ItemValue
PlanWeb Analytics Plus ($10/month add-on)
PagesAll 9 sv-website pages
Integration/_vercel/insights/script.js (handles both Web Analytics and Speed Insights)
Reporting Window24 months (Plus tier)
DashboardVercel Dashboard > sv-website > Analytics

Deprecated script path removed (Mar 21, 2026)

The old /_vercel/analytics/script.js path was returning 404. Vercel Web Analytics now operates through /_vercel/insights/script.js. The dead script tag was removed from all 9 HTML files.

Vercel Speed Insights

Tracks Core Web Vitals (LCP, FID, CLS) from real user sessions. Loaded via <script defer src="/_vercel/insights/script.js"></script> on all pages. Dashboard at Vercel > sv-website > Speed Insights.

Script-Tag API Pattern

Do NOT use window.va.track()

The .track() method only exists in the npm package (@vercel/analytics). The script-tag integration sets window.va as a function, not an object. Using .track() throws TypeError: window.va?.track is not a function.

// CORRECT — script-tag API
window.va?.('event', { name: 'signup_submit' });
window.va?.('event', { name: 'cta_click', data: { text: 'Get Started', location: 'hero' } });

// WRONG — npm package API (will throw TypeError)
window.va?.track('signup_submit');
window.va?.track('cta_click', { text: 'Get Started' });

Custom Events Reference

All custom events fire through Vercel Web Analytics using the window.va?.('event', ...) pattern. GA4 does not receive these — it relies on Enhanced Measurement for automatic event capture.

Landing Page (index.html)

Event NameTriggerData
promo_context_landingVisitor arrives with promo context (from a promo page CTA)slug, promo, src
signup_submitSignup form submission
signup_checkoutStripe checkout redirect
signup_waitlistWaitlist form submissioncity, zip
cta_clickCTA button clicktext, location
faq_openFAQ accordion openedquestion
scroll_depthPage scroll milestonesdepth (25%, 50%, 75%, 100%)

Partnerships Page (partnerships.html)

Event NameTriggerData
cta_clickCTA button clicktext, location
faq_openFAQ accordion openedquestion
scroll_depthPage scroll milestonesdepth (25%, 50%, 75%, 100%)
partnership_form_submitPartnership inquiry form submittedproperty
partnership_form_successForm submission succeededproperty
partnership_form_errorForm submission failedproperty, error

Promo Pages (promo/index.html)

Event NameTriggerData
promo_page_viewPromo page loadedslug, property, code, view, src
promo_cta_clickPromo CTA clickedslug, property, destination, view, src
promo_share_copyShare link copied to clipboard (PM view only)slug, view, src
promo_link_copyPromo link copied (PM view only)slug, view, src

The view field distinguishes resident vs pm views. The src field tracks attribution origin (e.g., email, link, direct).

Partnership Campaign Measurement

Storage Valet's growth strategy is partnership-driven: property management companies in Hudson County act as distribution channels to reach residents. Analytics is the system that validates whether this strategy is working.

The Measurement Chain

Each outreach wave generates measurable signals across the stack:

  1. Outreach email sent (HubSpot) — open/click tracked by HubSpot Sales extension
  2. PM clicks partnership link — UTM params (utm_source, utm_medium, utm_campaign) captured by GA4 on partnerships page
  3. PM submits partnership formpartnership_form_submit event fires in Vercel Analytics; UTM params flow to HubSpot deal via Resend webhook
  4. PM views promo pagepromo_page_view with view: pm fires; PM shares link with residents
  5. Resident visits promo pagepromo_page_view with view: resident and src attribution fires
  6. Resident clicks CTApromo_cta_click fires, then promo_context_landing on the landing page
  7. Resident signs upsignup_submit and signup_checkout events fire

Key Questions Analytics Can Answer

QuestionWhere to LookWhat to Check
Are outreach emails being opened?HubSpot deal activityOpen/click rates on deal timeline
Are PMs visiting the partnerships page?GA4 Acquisition > Traffic acquisitionFilter by utm_campaign matching wave name
Are PMs submitting partnership inquiries?Vercel Analytics > Eventspartnership_form_submit count
Are promo pages being viewed by residents?Vercel Analytics > Eventspromo_page_view with view: resident
Which properties drive the most traffic?Vercel Analytics > Eventspromo_page_view grouped by slug
Are residents converting from promo pages?Vercel Analytics > Eventspromo_context_landingsignup_submit correlation
Which outreach wave performed best?GA4 AcquisitionCompare utm_campaign values across waves

Current State (Mar 2026)

Wave 1A outreach was sent Mar 17, 2026 to 15 properties. Wave 1B was sent Mar 21, 2026 to 12 properties (7 in Batch 1, 5 in Batch 2). Wave 2 test send Mar 22, 2026 to 2 properties (CMPND, The Alexander). Total Automation Engine outreach: 29 properties contacted. The analytics infrastructure documented on this page is what enables performance review — GA4 UTM data shows whether outreach emails drove partnerships page visits, and Vercel custom events show whether those visits converted to form submissions and downstream resident signups.

UTM Campaign Attribution

GA4 automatically captures UTM parameters from URLs and surfaces them in Acquisition reports. No additional code is needed — GA4's Enhanced Measurement handles this natively.

UTM Parameters

ParameterPurposeExample
utm_sourceWhere the traffic came fromhubspot, email, google
utm_mediumMarketing channelemail, cpc, social
utm_campaignSpecific campaign namewave1a_outreach, spring_promo

Partnership Form UTM Capture

The partnerships page (partnerships.html) auto-captures UTM parameters from the URL and submits them alongside the partnership inquiry form. This enables CRM attribution — when a property manager clicks a link in an outreach email, the UTM params flow through to HubSpot.

// Hidden form fields populated from URL params
form.querySelector('[name="utm_source"]').value = getParam('utm_source');
form.querySelector('[name="utm_medium"]').value = getParam('utm_medium');
form.querySelector('[name="utm_campaign"]').value = getParam('utm_campaign');

Promo Page Source Attribution

Promo pages use a ?src=pm parameter to distinguish PM-originated share links from resident links. When a property manager shares a promo page, the ?pm=true toggle activates the PM view, and share links include ?src=pm for attribution. The ?pm=true parameter is stripped from the URL bar via history.replaceState so the clean URL can be shared.

GA4 MCP Tools

The GA4 Analytics MCP (mcp__analytics-mcp__*) provides read-only access to GA4 reports and property metadata from Claude Code sessions.

Infrastructure

ItemValue
Google Cloud Projectstorage-valet-analytics
OAuth ClientSV Analytics MCP (Desktop app type)
Client JSON~/.config/gcloud/ga4-oauth-client.json
ADC Token~/.config/gcloud/application_default_credentials.json
APIs EnabledAnalytics Admin API, Analytics Data API
1Password ItemGoogle Cloud - GA4 Analytics MCP
Scopesanalytics.readonly, cloud-platform (read-only)

Re-authenticating

If the ADC token expires (the MCP returns a 503 “Reauthentication is needed” error), re-run:

CLOUDSDK_PYTHON=/opt/homebrew/bin/python3.13 gcloud auth application-default login \
  --scopes=https://www.googleapis.com/auth/analytics.readonly,https://www.googleapis.com/auth/cloud-platform \
  --client-id-file=/Users/zacharybrown/.config/gcloud/ga4-oauth-client.json

Use the full path for --client-id-file (not ~). A browser window will open for Google OAuth.

MCP Limitations

The GA4 analytics MCP is read-only for reports and property metadata only. It cannot inspect admin-level settings including internal traffic rules, data filters, tag configuration, or data stream settings. When asked about GA4 admin configuration, always qualify that MCP tools cannot verify it — recommend checking the GA4 admin UI directly.

Appendix: Industry Benchmarks

Reference benchmarks for B2B cold email outreach to property management companies, compiled from industry reports (Instantly, Belkins, HubSpot, Martal Group, 2025–2026 data). SV's outreach is tighter-ICP than typical B2B cold email (local, hyper-relevant), so performance should skew toward the higher end of these ranges once follow-up sequences are in place.

Email-Level Metrics

MetricInadequateAverageHigh-PerformingNotes
Open rate<25%30–40%45%+Real estate industry avg ~42%; Apple Mail Privacy inflates opens — treat cautiously
Click-through rate<2%3–5%8%+More reliable signal than open rate
Reply rate<2%3–5%8–15%42% of all replies come from follow-ups; single-touch waves will underperform
Bounce rate>10%3–5%<2%Verify emails before sending; Wave 1A had 27% bounce (4/15)

Funnel-Level Metrics (Per Wave)

MetricInadequateAverageHigh-PerformingNotes
Email → promo page visit<5%10–15%20%+Requires UTM tracking on all email links
PM share tool engagement01–2 per wave3+ per wavepromo_share_copy / promo_link_copy events — strongest signal of PM distribution
Partnership form submission01 per 20 sent1 per 10 sentpartnership_form_submit events
Meeting booked0 per wave1 per 20–30 sent1 per 10 sent~1–2% meeting rate is typical for cold B2B
Resident signup per partnered property013+Full funnel: promo_context_landingsignup_checkout

When to Stop or Pivot

Need 50–100+ sends across 2–3 waves to establish SV's own baseline. 27 sends across Wave 1A + 1B is approaching but not yet sufficient for statistical significance.