Add Web Vitals performance tracking (LCP, INP, CLS, FCP, TTFB)

End-to-end performance metrics tracking: dedicated database table with
percentile aggregation, tracker collection behind data-perf attribute,
/api/send handling, report API endpoints, and Performance dashboard page
with threshold-based metric cards, time-series chart, and per-page breakdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mike CaoandClaude Opus 4.6 committed 2026-02-08 19:25:20 -08:00
1 parent 8cd3c03702
commit ce9e2416fb
24 files changed
+1028 -8

No files matched your search

+9
View File
@@ -98,6 +98,15 @@ export const FILTER_COLUMNS = {
export const COLLECTION_TYPE = {
event: 'event',
identify: 'identify',
performance: 'performance',
} as const;
export const WEB_VITALS_THRESHOLDS = {
lcp: { good: 2500, poor: 4000, unit: 'ms' },
inp: { good: 200, poor: 500, unit: 'ms' },
cls: { good: 0.1, poor: 0.25, unit: '' },
fcp: { good: 1800, poor: 3000, unit: 'ms' },
ttfb: { good: 800, poor: 1800, unit: 'ms' },
} as const;
export const EVENT_TYPE = {
+13
View File
@@ -112,6 +112,7 @@ export const reportTypeParam = z.enum([
'funnel',
'goal',
'journey',
'performance',
'retention',
'revenue',
'utm',
@@ -200,6 +201,17 @@ export const utmReportSchema = z.object({
}),
});
export const performanceReportSchema = z.object({
type: z.literal('performance'),
parameters: z.object({
startDate: z.coerce.date(),
endDate: z.coerce.date(),
unit: unitParam.optional(),
timezone: timezoneParam.optional(),
metric: z.enum(['lcp', 'inp', 'cls', 'fcp', 'ttfb']).optional(),
}),
});
export const revenueReportSchema = z.object({
type: z.literal('revenue'),
parameters: z.object({
@@ -244,6 +256,7 @@ export const reportTypeSchema = z.discriminatedUnion('type', [
goalReportSchema,
funnelReportSchema,
journeyReportSchema,
performanceReportSchema,
retentionReportSchema,
utmReportSchema,
revenueReportSchema,