SSMETRICS

Complete Documentation

Everything you need to know about SSMetrics analytics tracking, metrics, and verification.

Quick Start

  1. Get tracking ID — Go to /ssmetrics and generate a new tracking ID
  2. Copy magic prompt — (Optional) Describe your aha moment for TTV tracking, then copy the prompt
  3. Paste in coding agent — Use Cursor, Windsurf, or Claude Code to execute the prompt
  4. View dashboard — Go to /ssmetrics/your-tracking-id to see live metrics

How It Works

SSMetrics works in three layers:

1. Client Script (t.js)

Loaded on your app. Tracks page views and events, detects Session 2 returns automatically.

2. Backend API (/api/track)

Receives events from t.js. Validates data, extracts metadata (TTV), stores in PostgreSQL.

3. Dashboard (/ssmetrics/[id])

Queries the events table with SQL functions. Calculates activation, retention, funnel metrics.

All Metrics Explained

Unique Visitors

Count of distinct IP addresses or session IDs that visited your app.

Why: Are people finding you? Growth signal #1.

Tracking: Automatic on page load

Page Views

Total number of page loads across all visitors.

Why: Are people exploring or bouncing immediately?

Tracking: Automatic on page load

Bounce Rate

% of visitors who viewed only one page. Calculated as: (single-page sessions / total sessions) × 100.

Why: Is your landing page compelling enough to explore?

Tracking: Automatic (one page = bounce)

Top Sources

Which referrer (Google, Twitter, direct) sent the most traffic.

Why: Which channels work? Double down on what drives visitors.

Tracking: Automatic from document.referrer

Top Pages

Which pages/routes get the most views.

Why: What content resonates? Focus on high-traction pages.

Tracking: Automatic (you pass pathname)

Visitor → Signup

% of all visitors who clicked signup. Formula: (signups / visitors) × 100.

Why: Conversion metric. Low? Fix landing page or CTA.

Tracking: You call track('signup_click') and track('signup_success')

CTA Rate

% of page views where visitor clicked your call-to-action button.

Why: Are visitors engaging with your CTA? Low = fix copy or placement.

Tracking: You call track('pro_click') or custom events

Free → Paid

% of signups who become paying customers. Formula: (payments / signups) × 100.

Why: Revenue conversion. Highest-value metric.

Tracking: You call track('payment_success')

Avg Time to Value (TTV)

Seconds from page load to when user reaches their aha moment. Example: user creates first board.

Why: Lower TTV = faster product adoption = stickier product.

Tracking: You call track('app_activated', JSON.stringify({ttv_seconds: 45}))

Activation Rate

% of visitors who reach your app's aha moment. Formula: (activated / visitors) × 100.

Why: Most important conversion metric. If low, product UX is confusing.

Tracking: You call track('app_activated') when user completes aha moment

Fast vs Friction

Split of activators by speed: Fast = reached aha moment in under 2 min, Friction = took 2+ min.

Why: Identify slow onboarding paths. High friction = users struggling.

Tracking: Calculated from TTV (automatic once you track app_activated)

Session 2 Momentum

% of visitors who return within 48 hours. Formula: (returns within 48h / eligible visitors) × 100. Only counts visitors whose first visit was >48h ago.

Why: Clearest signal your app solves a real problem. >20% = genuine utility.

Tracking: Automatic via t.js (no code needed)

Dead Ends

Pages where users exit without activating, ranked by drop-off count. Shows average time spent on each dead-end page with smart signals: ⚡ Quick bounce (<10s = page confuses them), 🔄 Getting stuck (>60s = page too complex).

Why: Identifies conversion killers. The pages killing your activation rate. Fix UX, copy, or complexity on these pages.

Tracking: Automatic via t.js (fires on tab close/navigate without activation)

Session 2 Tracking (Automatic Retention)

Session 2 Momentum is tracked entirely by t.jsno code needed from you.

How It Works

  1. On first visit, t.js sets ss_first_seen in localStorage
  2. On every subsequent page load, t.js checks: "How long since first visit?"
  3. If gap is between 5 minutes and 48 hours: fires session_2_return event once
  4. The event includes hours_to_return (how many hours elapsed)

Why 5 min to 48h? — 5 minutes prevents bounces (quick tab closes), 48 hours is enough to prove genuine interest.

Why > 20% is "high utility": If more than 1-in-5 visitors come back on their own (no email, no reminder), your app solves a real problem people want to use repeatedly.

Dead Ends (Friction Detection)

Dead Ends shows which pages are killing your conversion. These are pages where users exit without completing your app's aha moment (without activating).

How It Works

  1. User visits pages in your app, t.js tracks dwell time (seconds per page)
  2. User exits (closes tab, navigates away) WITHOUT calling track('app_activated')
  3. t.js fires friction_bounce event with: last_page, pages_visited, time_on_last_page, session_duration
  4. Dashboard groups by last_page and shows drop-off count + avg time spent

Reading the Signals

  • ⚡ Quick bounce (<10s avg): Page confuses or doesn't appeal to visitors. Fix: copy, headline, or design.
  • 🔄 Getting stuck (>60s avg): Users read/explore but get lost or stuck. Fix: clearer CTA, simplify form, remove friction.
  • 🔄 Medium (10-60s avg): Users are deciding or reading. Fix: strengthen value proposition or remove objections.

Example: Your pricing page shows 24 drop-offs, 12s avg time. Quick bounce signal = copy doesn't resonate. Raise value or lower price. If it was 120s avg, users are reading but not convinced = strengthen benefits or testimonials.

Minimum threshold: Pages need 2+ drop-offs to appear (prevents noise from single bounces).

Testing & Verification

Test Page View Tracking

  1. Open your app in browser
  2. DevTools → Network tab → filter by 'sandrobuilds'
  3. Reload page — should see POST request with 'page_view' event
  4. Click a link → should see another page_view request

Test Session 2 Event

  1. Open DevTools → Application → Local Storage
  2. Verify ss_first_seen exists (timestamp)
  3. In Console: localStorage.setItem('ss_first_seen', String(Date.now() - 1000*60*60*12)) [simulates 12h ago]
  4. In Console: localStorage.removeItem('ss_session2_tracked')
  5. Reload page → Network tab should show session_2_return event

Verify Database

  1. Open Neon Postgres console
  2. Run: SELECT COUNT(*) FROM events WHERE event = 'session_2_return'
  3. Run: SELECT * FROM get_retention_stats('your-tracking-id')
  4. Verify session2_count, eligible_visitors, retention_rate

View on Dashboard

  1. Go to /ssmetrics/your-tracking-id
  2. Scroll to 'Activation & Retention' section
  3. Check 'Session 2 Momentum' shows percentage and progress bar
  4. If >20%: should see 'HIGH UTILITY' badge

Test Dead Ends Detection

  1. Open your app in incognito/private window (fresh session)
  2. Visit 2-3 pages and spend time on each
  3. Close the browser tab WITHOUT triggering app_activated event
  4. Go to /ssmetrics/your-tracking-id → scroll to 'Dead Ends' section
  5. Should see the page you exited from listed with 1 drop-off + avg time spent
  6. Repeat test 2-3 times on same page → drop-off count increases, avg time is recalculated

Best Practices

DO: Define your aha moment clearlyThe more specific you are ('user creates first board'), the better TTV tracking will be.

DO: Add TrackPage to every routeMissing even one route causes bounce rate errors. Check every /path in your app.

DO: Track real business actionsOnly add events for features that actually exist. Empty signup forms break your funnel.

DON'T: Create your own track() functionThe magic prompt's window.track is the ONLY way events reach the backend.

DON'T: Add TrackPage inside conditionalsPut it outside if/else so it fires exactly once per page load.

DON'T: Wait for localStorage in your trackingt.js handles all localStorage logic. Just call window.track().

Troubleshooting

⚠️ No events showing in dashboard

  • Is t.js script loading? Check Network tab for /t.js
  • Is window.track() being called? Add: console.log(window.track)
  • Did you add TrackPage to routes? Check every page has <TrackPage />
  • Are events hitting /api/track? Filter Network by 'sandrobuilds'

⚠️ Session 2 Momentum shows 0%

  • Only shows data after 48h of traffic. Are your visitors old enough?
  • Check: SELECT * FROM get_retention_stats('your-id')
  • eligible_visitors = 0? No visitors from >48h ago yet
  • eligible_visitors > 0 but session2_count = 0? Visitors haven't returned yet

⚠️ TTV always shows 0 or missing

  • Did you add app_activated events? Need track('app_activated', ...)
  • Did you include ttv_seconds? Format: JSON.stringify({ttv_seconds: 45})
  • Check Network: does the event body have ttv_seconds?
  • Check database: SELECT page FROM events WHERE event = 'app_activated' LIMIT 1

⚠️ Bounce rate is 100%

  • Every visitor is bouncing (1 page only). Check TrackPage is on EVERY route.
  • Did you miss /onboarding, /dashboard, or other pages?
  • React Strict Mode: check if TrackPage is inside a useEffect double-mount

Questions? Visit /ssmetrics to get started.

powered by sandrobuilds.com