Real-time Analytics - Common Issues & Fixes
✅ Fixed: Visitor Double-Counting
Problem
When a visitor navigates between pages, they were counted as multiple visitors instead of one.
Root Cause
The system was counting events instead of unique visitor_ids.
Solution
Backend (src/api/admin/analytics/live/route.ts)
// ❌ WRONG: Count sessions (can have multiple per visitor)
const currentVisitors = activeSessions.length;
// ✅ CORRECT: Count unique visitor_ids
const uniqueVisitorIds = new Set(recentEvents.map(e => e.visitor_id));
const currentVisitors = uniqueVisitorIds.size;
Frontend (src/admin/components/websites/live-analytics-panel.tsx)
// ❌ WRONG: Increment counter on each event
currentVisitors: prev.currentVisitors + 1
// ✅ CORRECT: Recalculate from unique visitor_ids
const uniqueVisitors = new Set(
recentEvents.map(e => e.visitor_id).filter(Boolean)
);
currentVisitors: uniqueVisitors.size
How It Works Now
Visitor browses:
/home (10:00) → visitor_abc
/products (10:01) → visitor_abc
/about (10:02) → visitor_abc
Old behavior:
Current visitors: 3 ❌ (counted each pageview)
New behavior:
Current visitors: 1 ✅ (unique visitor_id)
Active Page Tracking
How It Determines Current Page
// Get most recent event per visitor
const visitorCurrentPages = new Map<string, string>();
// Events sorted newest first
for (const event of sortedEvents) {
if (!visitorCurrentPages.has(event.visitor_id)) {
// First occurrence = most recent page
visitorCurrentPages.set(event.visitor_id, event.pathname);
}
}
// Count visitors per page
const activePages = {};
for (const pathname of visitorCurrentPages.values()) {
activePages[pathname] = (activePages[pathname] || 0) + 1;
}
Example
Events (newest first):
1. visitor_abc → /products (10:02)
2. visitor_abc → /about (10:01)
3. visitor_abc → /home (10:00)
4. visitor_xyz → /home (10:01)
Active pages:
/products: 1 (visitor_abc is here now)
/home: 1 (visitor_xyz is here now)
Testing
Verify Unique Counting
// 1. Open website in browser
// 2. Open Live Analytics in admin
// 3. Navigate: /home → /products → /about
// 4. Check Live Analytics
Expected:
Current Visitors: 1 ✅
Active Pages: /about (1 viewing) ✅
Not:
Current Visitors: 3 ❌
Active Pages: /home (1), /products (1), /about (1) ❌
Multi-Visitor Test
// 1. Open website in Browser 1
// 2. Open website in Browser 2 (incognito)
// 3. Navigate both browsers
Browser 1: /home → /products
Browser 2: /about
Expected in Live Analytics:
Current Visitors: 2 ✅
Unique Visitors: 2 ✅
Active Pages:
/products: 1
/about: 1
Implementation Details
Time Window
// Count visitors active in last 5 minutes
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
const recentEvents = await getEvents({
timestamp: { $gte: fiveMinutesAgo }
});
Why 5 Minutes?
- ✅ Heartbeat every 30 seconds
- ✅ 10 heartbeats in 5 minutes
- ✅ Enough buffer for network delays
- ✅ Not too long (stale visitors)
Visitor Lifecycle
Visitor arrives:
00:00 - First pageview
00:30 - Heartbeat
01:00 - Heartbeat
01:30 - Navigate to new page
02:00 - Heartbeat
...
05:00 - Last heartbeat
After 05:00:
No more heartbeats
After 05:00 + 5min buffer = 10:00
Visitor removed from "current" count
Edge Cases Handled
1. Rapid Navigation
Visitor navigates quickly:
/home → /products → /about (all within 1 second)
Result:
Only counts as 1 visitor ✅
Shows on /about (most recent) ✅
2. Multiple Tabs
Same visitor opens 2 tabs:
Tab 1: /home
Tab 2: /products
Result:
Counts as 1 visitor ✅ (same visitor_id)
Shows on most recent page ✅
3. Session Timeout
Visitor inactive for 30+ minutes:
Session ends
New session starts on return
Result:
Still same visitor_id ✅
New session_id ✅
Counts as 1 unique visitor ✅
Performance
Memory Usage
// Store last 20 events in frontend
recentEvents: [...prev.recentEvents].slice(0, 20)
// ~20 events × 500 bytes = ~10 KB
// Negligible memory usage ✅
Calculation Overhead
// Recalculate on each event
const uniqueVisitors = new Set(recentEvents.map(e => e.visitor_id));
// O(n) where n = 20 events
// ~0.1ms on modern browsers
// Imperceptible ✅
Monitoring
Check Accuracy
-- Backend query
SELECT
COUNT(DISTINCT visitor_id) as unique_visitors,
COUNT(DISTINCT session_id) as unique_sessions
FROM analytics_event
WHERE website_id = 'xxx'
AND timestamp > NOW() - INTERVAL '5 minutes';
Compare with UI
// Frontend console
console.log('Current Visitors:', liveData.currentVisitors);
console.log('Unique Visitors:', liveData.uniqueVisitors);
// Should match backend query ✅
Summary
What Was Fixed
✅ Visitor counting - Now counts unique visitor_ids, not events ✅ Active pages - Shows visitor's current page, not all visited pages ✅ Real-time updates - Recalculates on each event for accuracy ✅ Edge cases - Handles rapid navigation, multiple tabs, etc.
How It Works
- Track events with visitor_id and session_id
- Get events from last 5 minutes
- Count unique visitor_ids (not events!)
- Determine current page from most recent event per visitor
- Display accurate live stats
Result
🎉 Accurate real-time visitor tracking!
- Same visitor navigating = 1 visitor ✅
- Multiple visitors = correct count ✅
- Active pages = current location ✅
- Updates in real-time ✅