fledged out analytics, self gather, remove vercel speed insights

This commit is contained in:
Michael Freno
2026-01-06 01:34:55 -05:00
parent b118a62f83
commit ea556b3677
13 changed files with 867 additions and 114 deletions

View File

@@ -15,6 +15,14 @@ export interface AnalyticsEntry {
os?: string | null;
sessionId?: string | null;
durationMs?: number | null;
fcp?: number | null;
lcp?: number | null;
cls?: number | null;
fid?: number | null;
inp?: number | null;
ttfb?: number | null;
domLoad?: number | null;
loadComplete?: number | null;
}
export async function logVisit(entry: AnalyticsEntry): Promise<void> {
@@ -23,8 +31,9 @@ export async function logVisit(entry: AnalyticsEntry): Promise<void> {
await conn.execute({
sql: `INSERT INTO VisitorAnalytics (
id, user_id, path, method, referrer, user_agent, ip_address,
country, device_type, browser, os, session_id, duration_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
country, device_type, browser, os, session_id, duration_ms,
fcp, lcp, cls, fid, inp, ttfb, dom_load, load_complete
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
args: [
uuid(),
entry.userId || null,
@@ -38,7 +47,15 @@ export async function logVisit(entry: AnalyticsEntry): Promise<void> {
entry.browser || null,
entry.os || null,
entry.sessionId || null,
entry.durationMs || null
entry.durationMs || null,
entry.fcp || null,
entry.lcp || null,
entry.cls || null,
entry.fid || null,
entry.inp || null,
entry.ttfb || null,
entry.domLoad || null,
entry.loadComplete || null
]
});
} catch (error) {
@@ -308,6 +325,121 @@ export async function getPathAnalytics(
};
}
export async function getPerformanceStats(days: number = 30): Promise<{
avgLcp: number | null;
avgFcp: number | null;
avgCls: number | null;
avgInp: number | null;
avgTtfb: number | null;
avgDomLoad: number | null;
avgLoadComplete: number | null;
p75Lcp: number | null;
p75Fcp: number | null;
totalWithMetrics: number;
byPath: Array<{
path: string;
avgLcp: number;
avgFcp: number;
avgCls: number;
avgTtfb: number;
count: number;
}>;
}> {
const conn = ConnectionFactory();
// Get average metrics
const avgResult = await conn.execute({
sql: `SELECT
AVG(lcp) as avgLcp,
AVG(fcp) as avgFcp,
AVG(cls) as avgCls,
AVG(inp) as avgInp,
AVG(ttfb) as avgTtfb,
AVG(dom_load) as avgDomLoad,
AVG(load_complete) as avgLoadComplete,
COUNT(*) as total
FROM VisitorAnalytics
WHERE created_at >= datetime('now', '-${days} days')
AND fcp IS NOT NULL`,
args: []
});
const avgRow = avgResult.rows[0] as any;
// Get 75th percentile for LCP and FCP (approximation using median)
const p75LcpResult = await conn.execute({
sql: `SELECT lcp as p75
FROM VisitorAnalytics
WHERE created_at >= datetime('now', '-${days} days')
AND lcp IS NOT NULL
ORDER BY lcp
LIMIT 1 OFFSET (
SELECT COUNT(*) * 75 / 100
FROM VisitorAnalytics
WHERE created_at >= datetime('now', '-${days} days')
AND lcp IS NOT NULL
)`,
args: []
});
const p75FcpResult = await conn.execute({
sql: `SELECT fcp as p75
FROM VisitorAnalytics
WHERE created_at >= datetime('now', '-${days} days')
AND fcp IS NOT NULL
ORDER BY fcp
LIMIT 1 OFFSET (
SELECT COUNT(*) * 75 / 100
FROM VisitorAnalytics
WHERE created_at >= datetime('now', '-${days} days')
AND fcp IS NOT NULL
)`,
args: []
});
// Get performance by path (only for non-API paths)
const byPathResult = await conn.execute({
sql: `SELECT
path,
AVG(lcp) as avgLcp,
AVG(fcp) as avgFcp,
AVG(cls) as avgCls,
AVG(ttfb) as avgTtfb,
COUNT(*) as count
FROM VisitorAnalytics
WHERE created_at >= datetime('now', '-${days} days')
AND fcp IS NOT NULL
AND path NOT LIKE '/api/%'
GROUP BY path
ORDER BY count DESC
LIMIT 20`,
args: []
});
const byPath = byPathResult.rows.map((row: any) => ({
path: row.path,
avgLcp: row.avgLcp || 0,
avgFcp: row.avgFcp || 0,
avgCls: row.avgCls || 0,
avgTtfb: row.avgTtfb || 0,
count: row.count
}));
return {
avgLcp: avgRow?.avgLcp || null,
avgFcp: avgRow?.avgFcp || null,
avgCls: avgRow?.avgCls || null,
avgInp: avgRow?.avgInp || null,
avgTtfb: avgRow?.avgTtfb || null,
avgDomLoad: avgRow?.avgDomLoad || null,
avgLoadComplete: avgRow?.avgLoadComplete || null,
p75Lcp: (p75LcpResult.rows[0] as any)?.p75 || null,
p75Fcp: (p75FcpResult.rows[0] as any)?.p75 || null,
totalWithMetrics: avgRow?.total || 0,
byPath
};
}
export async function cleanupOldAnalytics(
olderThanDays: number
): Promise<number> {