Analytics
GA4, Vercel Analytics, traffic filtering, custom events, and campaign attribution
Last updated: Apr 18, 2026
Analytics Stack Overview
Three analytics products run on all sv-website pages. Each serves a distinct purpose.
| Platform | Cost | Primary Use | Retention |
|---|---|---|---|
| Google Analytics 4 (GA4) | Free | Behavioral analytics, acquisition channels, UTM attribution, engagement time, conversion funnels | 14 months (default) |
| Vercel Web Analytics | $10/month (Plus tier) | Custom event tracking, privacy-friendly visitor counts, referrer breakdown | 24 months |
| Vercel Speed Insights | Included with Pro | Core Web Vitals (LCP, FID, CLS), page load performance | Rolling |
When to Use Which
| Question | Use |
|---|---|
| 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
| Item | Value |
|---|---|
| Account | My Storage Valet LLC |
| Property | mystoragevalet.com |
| Property ID | 526241313 |
| Measurement ID | G-9SBYJ8QEC1 |
| Stream ID | 13680726133 |
| Stream Name | Storage Valet Website |
| Cost | Free (included with Google account) |
| Pages | All 9 sv-website pages (8 main + promo template) |
| Dashboard | analytics.google.com (sign in with zach@mystoragevalet.com) |
| Timezone | Eastern (US) |
| Industry | Business & Industrial |
| Data Collection Start | Feb 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:
| Event | What It Captures |
|---|---|
| Page views | Every page load and client-side navigation |
| Scrolls | When user scrolls past 90% of page |
| Outbound clicks | Clicks to external domains (e.g., portal.mystoragevalet.com, Stripe checkout) |
| Site search | Query parameters in URL (not currently used) |
| File downloads | Clicks on document/file links |
Still To Build Out
- GA4 conversion events: Mark key actions (e.g., signup form submission, Stripe checkout redirect) as conversions in GA4 for funnel tracking
- Google Ads linkage: When Google Ads is set up, link to this GA4 property for ad performance tracking
- Google Business Profile: Not yet created — needed for local search visibility, Google Maps listing, and review collection
- Meta (Facebook) Pixel: Not yet installed — needed if running Facebook/Instagram ads
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
| Item | Value |
|---|---|
| Rule Name | 1200 Clinton St (ZB's Home Network) |
| traffic_type Value | internal |
| IPv4 | 71.187.246.67/32 (CIDR, exact match) |
| IPv6 | 2600:4041:41ff:a200::/64 (CIDR, subnet) |
| ISP | Optimum/Cablevision (residential) |
| Location | GA4 Admin > Data Streams > Configure Tag Settings > Define Internal Traffic |
Data Filter
| Item | Value |
|---|---|
| Filter Name | Internal Traffic |
| Filter Type | Internal Traffic |
| Operation | Exclude |
| Parameter | traffic_type exactly matches internal |
| State | Active |
| Location | GA4 Admin > Data collection and modification > Data filters |
Coverage
| Scenario | Filtered? | Why |
|---|---|---|
| Any device on home Wi-Fi (Mac Studio, MacBook Air, phone) | Yes | Same public IP |
| Claude in Chrome from Zach's machine | Yes | Same public IP |
| Phone on cellular data | No | Carrier assigns rotating IPs |
| MacBook Air on other Wi-Fi (coffee shop, etc.) | No | Different public IP |
| AI cloud agents (NotebookLM, Perplexity, ChatGPT) | No | Cloud server IPs, rotating and unpredictable |
| Traditional bots (Googlebot, Bingbot) | N/A | GA4 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:
- Check current IPv4 at api.ipify.org
- Go to GA4 Admin > Data Streams > Storage Valet Website > Configure Tag Settings > Show More > Define Internal Traffic
- Edit the “1200 Clinton St” rule and update the IPv4 value
- For IPv6, search “what is my IP” in a browser to see the current IPv6 address and verify it still falls within the
/64subnet
Known Unfiltered Noise Sources
These appear in GA4 data and cannot be practically filtered. Volume is low (~20–30 sessions/week combined).
| Source | Typical City in GA4 | What It Is |
|---|---|---|
| NotebookLM | Various | Google AI tool analyzing the site; shows as notebooklm.google.com referrer |
| Microsoft Azure bots | Moses Lake, WA | AI agents running on Azure data center infrastructure |
| Google/tech infrastructure | San Jose, CA | Various Google services and crawlers |
| Other cloud bots | Chicago, 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
| Item | Value |
|---|---|
| Plan | Web Analytics Plus ($10/month add-on) |
| Pages | All 9 sv-website pages |
| Integration | /_vercel/insights/script.js (handles both Web Analytics and Speed Insights) |
| Reporting Window | 24 months (Plus tier) |
| Dashboard | Vercel 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 Name | Trigger | Data |
|---|---|---|
promo_context_landing | Visitor arrives with promo context (from a promo page CTA) | slug, promo, src |
signup_submit | Signup form submission | — |
signup_checkout | Stripe checkout redirect | — |
signup_waitlist | Waitlist form submission | city, zip |
cta_click | CTA button click | text, location |
faq_open | FAQ accordion opened | question |
scroll_depth | Page scroll milestones | depth (25%, 50%, 75%, 100%) |
Partnerships Page (partnerships.html)
| Event Name | Trigger | Data |
|---|---|---|
cta_click | CTA button click | text, location |
faq_open | FAQ accordion opened | question |
scroll_depth | Page scroll milestones | depth (25%, 50%, 75%, 100%) |
partnership_form_submit | Partnership inquiry form submitted | property |
partnership_form_success | Form submission succeeded | property |
partnership_form_error | Form submission failed | property, error |
Promo Pages (promo/index.html)
| Event Name | Trigger | Data |
|---|---|---|
promo_page_view | Promo page loaded | slug, property, code, view, src |
promo_cta_click | Promo CTA clicked | slug, property, destination, view, src |
promo_share_copy | Share link copied to clipboard (PM view only) | slug, view, src |
promo_link_copy | Promo 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:
- Outreach email sent (HubSpot) — open/click tracked by HubSpot Sales extension
- PM clicks partnership link — UTM params (
utm_source,utm_medium,utm_campaign) captured by GA4 on partnerships page - PM submits partnership form —
partnership_form_submitevent fires in Vercel Analytics; UTM params flow to HubSpot deal via Resend webhook - PM views promo page —
promo_page_viewwithview: pmfires; PM shares link with residents - Resident visits promo page —
promo_page_viewwithview: residentandsrcattribution fires - Resident clicks CTA —
promo_cta_clickfires, thenpromo_context_landingon the landing page - Resident signs up —
signup_submitandsignup_checkoutevents fire
Key Questions Analytics Can Answer
| Question | Where to Look | What to Check |
|---|---|---|
| Are outreach emails being opened? | HubSpot deal activity | Open/click rates on deal timeline |
| Are PMs visiting the partnerships page? | GA4 Acquisition > Traffic acquisition | Filter by utm_campaign matching wave name |
| Are PMs submitting partnership inquiries? | Vercel Analytics > Events | partnership_form_submit count |
| Are promo pages being viewed by residents? | Vercel Analytics > Events | promo_page_view with view: resident |
| Which properties drive the most traffic? | Vercel Analytics > Events | promo_page_view grouped by slug |
| Are residents converting from promo pages? | Vercel Analytics > Events | promo_context_landing → signup_submit correlation |
| Which outreach wave performed best? | GA4 Acquisition | Compare 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
| Parameter | Purpose | Example |
|---|---|---|
utm_source | Where the traffic came from | hubspot, email, google |
utm_medium | Marketing channel | email, cpc, social |
utm_campaign | Specific campaign name | wave1a_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
| Item | Value |
|---|---|
| Google Cloud Project | storage-valet-analytics |
| OAuth Client | SV Analytics MCP (Desktop app type) |
| Client JSON | ~/.config/gcloud/ga4-oauth-client.json |
| ADC Token | ~/.config/gcloud/application_default_credentials.json |
| APIs Enabled | Analytics Admin API, Analytics Data API |
| 1Password Item | Google Cloud - GA4 Analytics MCP |
| Scopes | analytics.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
| Metric | Inadequate | Average | High-Performing | Notes |
|---|---|---|---|---|
| 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)
| Metric | Inadequate | Average | High-Performing | Notes |
|---|---|---|---|---|
| Email → promo page visit | <5% | 10–15% | 20%+ | Requires UTM tracking on all email links |
| PM share tool engagement | 0 | 1–2 per wave | 3+ per wave | promo_share_copy / promo_link_copy events — strongest signal of PM distribution |
| Partnership form submission | 0 | 1 per 20 sent | 1 per 10 sent | partnership_form_submit events |
| Meeting booked | 0 per wave | 1 per 20–30 sent | 1 per 10 sent | ~1–2% meeting rate is typical for cold B2B |
| Resident signup per partnered property | 0 | 1 | 3+ | Full funnel: promo_context_landing → signup_checkout |
When to Stop or Pivot
- Open rate below 25% across 2+ waves — deliverability or subject line problem
- Reply rate is 0% after 30+ sends with follow-ups — wrong ICP or value prop miss
- Zero PM share events after 3+ waves — PMs are not distributing to residents
- Bounce rate exceeds 10% — list quality problem, verify emails before sending
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.