- Date: 2026-04-30
- Author: Enes Shehu
- Milestone: M6 · Sprint 6
- Next.js: 15.5.14 (App Router, Turbopack dev / Webpack prod)
- Method:
npm run build && npm run startthennpx lighthouse@11 <url> --preset=desktop --only-categories=performance --chrome-flags="--headless --no-sandbox --disable-gpu"per route - Reproduce: see Verification below
This audit satisfies CP-149 acceptance criteria 1–7. All numbers below are from a single cold run on the current branch after the optimizations in Changes landed; CI (CP-150 follow-up) should re-run via npm run lighthouse to gate future regressions.
- Grafana — API latency:
devops/monitoring/grafana/dashboards/api-latency.json(p50/p95/p99 latency and throughput) - Grafana — Error rate:
devops/monitoring/grafana/dashboards/error-rate.json(5xx ratio and failing routes) - Grafana — Pod/container health:
devops/monitoring/grafana/dashboards/pod-health.json(CPU/RAM/restarts) - Loki log queries:
docs/logging-architecture.md#tracing-in-grafana-loki - Kibana (ELK):
http://localhost:5601for exploratory log search in the local observability stack
| Route | Perf | FCP (ms) | LCP (ms) | CLS | TBT (ms) | SI (ms) | Status |
|---|---|---|---|---|---|---|---|
/ |
100 | 357 | 624 | 0.002 | 0 | 357 | PASS |
/login |
100 | 351 | 774 | 0.000 | 0 | 351 | PASS |
/signup |
99 | 351 | 906 | 0.000 | 0 | 351 | PASS |
/discover |
99 | 351 | 966 | 0.005 | 0 | 798 | PASS |
/messages |
99 | 351 | 980 | 0.000 | 0 | 355 | PASS |
/admin/analytics |
99 | 349 | 876 | 0.000 | 0 | 917 | PASS |
/projects |
98 | 357 | 1166 | 0.000 | 0 | 378 | PASS |
/onboarding |
100 | 371 | 701 | 0.000 | 0 | 371 | PASS |
Budget vs actual (worst case across routes):
| Metric | Budget | Worst | Margin |
|---|---|---|---|
| Performance | >= 90 | 98 | +8 |
| FCP | < 1500ms | 371ms | -1129 |
| LCP | < 2500ms | 1166ms | -1334 |
| CLS | < 0.1 | 0.005 | -0.095 |
| TBT | < 200ms | 0ms | -200 |
All eight audited routes clear every CP-149 numerical target with substantial headroom.
| Route | Page | First Load JS | Status |
|---|---|---|---|
/ |
816 B | 106 kB | PASS |
/admin |
3.58 kB | 148 kB | PASS |
/admin/analytics |
3.53 kB | 179 kB | PASS |
/discover |
12 kB | 159 kB | PASS |
/login |
4.14 kB | 224 kB | PASS |
/messages |
22.7 kB | 202 kB | PASS |
/onboarding |
4.97 kB | 180 kB | PASS |
/onboarding/complete |
2.56 kB | 150 kB | PASS |
/profile/[id] |
5.46 kB | 153 kB | PASS |
/projects |
1.69 kB | 153 kB | PASS |
/projects/[id] |
2.75 kB | 154 kB | PASS |
/projects/[id]/standups |
4.11 kB | 196 kB | PASS |
/projects/new |
3.20 kB | 109 kB | PASS |
/signup |
4.39 kB | 224 kB | PASS |
Shared chunks (loaded once across all routes): 102 kB total — 46 kB framework + 54.2 kB react-dom + 2 kB other.
Largest route bundle: /login and /signup at 224 kB First Load JS — both 26 kB under the 250 kB criterion-6 budget. No chunk exceeds the threshold.
To regenerate the visual treemap: npm run analyze opens the @next/bundle-analyzer report in the browser.
@next/bundle-analyzerwired intonext.config.mjsbehindANALYZE=true. New script:npm run analyze.- Lighthouse CI config at
scripts/lighthouse.config.jsonwith the perf budget asserted (perf >= 0.9, FCP < 1500, LCP < 2500, CLS < 0.1, TBT warn < 200, unused-JS warn < 250 kB). Script:npm run lighthouse(invokesnpx --yes @lhci/cli@0.13.xto avoid a broken transitiveip-address@^10.1.1dep when installed locally).
optimizePackageImports— addedlucide-reactandframer-motioninnext.config.mjs.experimental. Tree-shakes barrel exports automatically; the largest single win for routes that pull in icon sets.- Image formats —
images.formats: ["image/avif", "image/webp"]innext.config.mjs. No raw<img>tags exist anywhere in the app today (criterion 5 already met content-wise); the format hint applies whenevernext/imageis introduced post-backend. - Viewport metadata — added
export const viewportinapp/layout.tsxso Lighthouse mobile-friendly + CLS audits don't penalize the missing meta tag. - Code-split heavy chart on
/admin/analytics—TrendChart(4 instances per analytics view) now loads vianext/dynamic({ ssr: false, loading: <skeleton/> }). Keeps SVG chart code out of the initial admin bundle. - Code-split chat surface on
/messages—ChatView(AnimatePresence +@microsoft/signalrclient) loads vianext/dynamic({ ssr: false, loading: <skeleton/> }). Defers SignalR client until user actually opens a thread. - Route-level streaming — added
loading.tsxto/discover,/messages,/admin/analytics,/projectsso the App Router streams the route shell while data resolves. First fourloading.tsxfiles in the codebase.
next/font/google(Inter,Space_Grotesk) withdisplay: "swap"inapp/layout.tsx— no FOIT.- Zero raw
<img>tags acrossapp/andcomponents/— initials andlucide-reacticons only. - Minimal
public/(5 SVGs, ~11 kB total) — no large raster assets to optimize. images.remotePatternsforavatars.githubusercontent.comalready configured.
/messagesSignalR connect cost — backend hub is not yet built (Sprint 3). Once it ships, re-audit/messageswith a real connection; if LCP regresses, lazy-init the connection on first user interaction rather than on mount./profile/[id]real avatars — currently renders initials. After backend wires GitHub avatar URLs, switch the<div>initials tonext/imagewithsizesset so AVIF/WebP serve.- CI gate —
npm run lighthouseruns locally but is not wired into.github/workflows/ci.yml. A follow-up infra ticket should add aperfjob that runs afterbuildand posts the LHCI report as a PR comment. Tracking under CP-150's load-test follow-up. - Mobile preset — this audit ran the desktop preset. CP-149 acceptance criteria do not specify; mobile should be added once real-device perf becomes a deliverable.
cd frontend/client
# 1. Type-check + bundle build
npm run type-check
npm run build # confirm "First Load JS shared by all" line + per-route table
# 2. Bundle analyzer (criterion 6 — no chunk > 250 kB)
npm run analyze # opens treemap in browser
# 3. Lighthouse audit (criteria 1-4)
npm run start & # background; waits on :3000
npm run lighthouse # runs config in scripts/lighthouse.config.json against 8 URLs
# 4. Spot-check streaming (criterion 7)
# open /discover, /messages, /admin/analytics, /projects in a throttled browser
# the loading.tsx skeleton should render before the page payload resolves| # | Criterion | Evidence |
|---|---|---|
| 1 | Lighthouse Performance >= 90 on all pages | Results table — worst score is 98 |
| 2 | First Contentful Paint < 1.5s | Worst FCP 371 ms (/onboarding) |
| 3 | Largest Contentful Paint < 2.5s | Worst LCP 1166 ms (/projects) |
| 4 | Cumulative Layout Shift < 0.1 | Worst CLS 0.005 (/discover) |
| 5 | Images optimized via next/image |
Zero raw <img>; AVIF/WebP formats configured |
| 6 | No single JS bundle > 250 kB | Largest First Load JS 224 kB (/login, /signup) |
| 7 | Lazy loading for below-fold | next/dynamic on TrendChart + ChatView; 4 loading.tsx files |