From 8649f37bb964a84bfbd62dc6fb45b550cea8c186 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 22 Mar 2026 19:52:49 +0100 Subject: [PATCH] feat(pagespeed): split diagnostics by category (Performance, Accessibility, Best Practices, SEO) Each Lighthouse category gets its own card with failing audits sorted by impact and collapsed passed audits. Matches pagespeed.web.dev layout. --- app/sites/[id]/pagespeed/page.tsx | 84 +++++++++++++++++++------------ lib/api/pagespeed.ts | 1 + 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/app/sites/[id]/pagespeed/page.tsx b/app/sites/[id]/pagespeed/page.tsx index 33d5f3e..c0b0f34 100644 --- a/app/sites/[id]/pagespeed/page.tsx +++ b/app/sites/[id]/pagespeed/page.tsx @@ -228,21 +228,32 @@ export default function PageSpeedPage() { score: c.performance_score, })) - // * Parse audits into groups + // * Parse audits into groups by Lighthouse category const audits = currentCheck?.audits ?? [] - const failingAudits = audits - .filter(a => a.category !== 'passed') - .sort((a, b) => { - // Opportunities first (sorted by savings_ms desc), then diagnostics - if (a.category === 'opportunity' && b.category !== 'opportunity') return -1 - if (a.category !== 'opportunity' && b.category === 'opportunity') return 1 - if (a.category === 'opportunity' && b.category === 'opportunity') { - return (b.savings_ms ?? 0) - (a.savings_ms ?? 0) - } - return 0 - }) const passed = audits.filter(a => a.category === 'passed') + const categoryGroups = [ + { key: 'performance', label: 'Performance' }, + { key: 'accessibility', label: 'Accessibility' }, + { key: 'best-practices', label: 'Best Practices' }, + { key: 'seo', label: 'SEO' }, + ] + + // * Build per-category failing audits, sorted by impact + const auditsByGroup: Record = {} + for (const group of categoryGroups) { + auditsByGroup[group.key] = audits + .filter(a => a.category !== 'passed' && a.group === group.key) + .sort((a, b) => { + if (a.category === 'opportunity' && b.category !== 'opportunity') return -1 + if (a.category !== 'opportunity' && b.category === 'opportunity') return 1 + if (a.category === 'opportunity' && b.category === 'opportunity') { + return (b.savings_ms ?? 0) - (a.savings_ms ?? 0) + } + return 0 + }) + } + // * Core Web Vitals metrics const metrics = [ { key: 'fcp', label: 'First Contentful Paint', value: currentCheck?.fcp_ms ?? null }, @@ -503,31 +514,38 @@ export default function PageSpeedPage() { )} - {/* Section 4 — Diagnostics */} + {/* Section 4 — Diagnostics by Category */} {audits.length > 0 && ( -
-

- Diagnostics -

+
+ {categoryGroups.map(group => { + const groupAudits = auditsByGroup[group.key] ?? [] + const groupPassed = passed.filter(a => a.group === group.key) + if (groupAudits.length === 0 && groupPassed.length === 0) return null + return ( +
+

+ {group.label} +

- {/* Failing audits — flat list sorted by impact */} - {failingAudits.length > 0 && ( -
- {failingAudits.map(audit => )} -
- )} + {groupAudits.length > 0 && ( +
+ {groupAudits.map(audit => )} +
+ )} - {/* Passed audits — collapsed */} - {passed.length > 0 && ( -
- - {passed.length} passed audit{passed.length !== 1 ? 's' : ''} - -
- {passed.map(audit => )} + {groupPassed.length > 0 && ( +
+ + {groupPassed.length} passed audit{groupPassed.length !== 1 ? 's' : ''} + +
+ {groupPassed.map(audit => )} +
+
+ )}
-
- )} + ) + })}
)}
diff --git a/lib/api/pagespeed.ts b/lib/api/pagespeed.ts index f3d811e..b036d52 100644 --- a/lib/api/pagespeed.ts +++ b/lib/api/pagespeed.ts @@ -20,6 +20,7 @@ export interface AuditSummary { display_value?: string savings_ms?: number category: 'opportunity' | 'diagnostic' | 'passed' + group?: string // "performance", "accessibility", "best-practices", "seo" details?: AuditDetailItem[] }