Files
cozsweet-frontend-nextjs/docs/performance-baseline.md
T
admin b4904e738b perf: defer heavy modules and add performance baselines
Lazy-load Stripe and the chat reply animation, remove the unused Lottie runtime, and add repeatable bundle and Web Vitals baseline tooling.
2026-07-14 10:55:08 +08:00

3.8 KiB
Raw Blame History

Performance baseline

Baseline date: 2026-07-14

Next.js: 16.2.7 (Turbopack)

This baseline covers two different signals:

  • Bundle analysis verifies whether expensive modules are on the eager or async client path.
  • Lab Web Vitals provide a repeatable mobile profile for detecting large regressions before field data is available.

Bundle baseline

Run:

pnpm perf:bundle

The command uses the built-in Next.js Turbopack analyzer and writes:

  • Interactive analysis: .next/diagnostics/analyze/
  • Machine-readable summary: .next/diagnostics/bundle-summary.json

Compressed sizes below are Turbopack module estimates. Eager is the route's synchronous client graph; async is code behind at least one dynamic import.

Route Eager before Eager baseline Change Async baseline
/splash 644.4 KiB 644.4 KiB 0.0 KiB 14.3 KiB
/chat 666.2 KiB 666.2 KiB 0.0 KiB 14.7 KiB
/subscription 660.7 KiB 657.0 KiB -3.7 KiB 20.7 KiB
/tip 653.9 KiB 650.4 KiB -3.5 KiB 20.7 KiB

Module loading baseline:

Module Before Baseline
@stripe/react-stripe-js eager async
@stripe/stripe-js eager async
Stripe payment dialog eager async
Chat reply animation eager async
lottie-react dependency present, not bundled removed

The total potential graph can grow slightly because an async chunk has its own loader metadata. The primary budget is the eager graph and the audited module phase, not the sum of eager and unopened async code.

Initial warning budgets are intentionally above the baseline and are not enforced by CI:

Route Eager client JS warning level
/splash 650 KiB
/chat 675 KiB
/subscription 665 KiB
/tip 660 KiB

Lab Web Vitals baseline

Build and measure the local production app:

pnpm build
pnpm perf:vitals

Default profile:

  • Pixel 7 viewport and user agent
  • 4× CPU slowdown
  • 1.6 Mbps download, 750 Kbps upload, 150 ms latency
  • Three cold browser contexts per route; the table reports the median
  • Service workers disabled to keep first-load measurements comparable

The default routes are /splash and /tip, which are stable without a stored login. The 2026-07-14 local baseline is:

Route TTFB FCP LCP CLS Synthetic INP Initial JS transfer
/splash 8.5 ms 840 ms 840 ms 0.000 24 ms 468.5 KiB
/tip 6.3 ms 828 ms 6,112 ms 0.010 n/a 473.7 KiB

/tip LCP is the first visible performance gap from this baseline and should be investigated separately. The synthetic INP value is produced by one body interaction and is only a regression signal; it does not replace field INP.

The report is written to .next/diagnostics/web-vitals-baseline.json. Useful overrides:

PERF_RUNS=5 pnpm perf:vitals
PERF_ROUTES=/splash,/tip,/chat pnpm perf:vitals
PERF_BASE_URL=https://frontend-test.banlv-ai.com pnpm perf:vitals
PERF_REPORT_PATH=./performance-report.json pnpm perf:vitals

The report records both the requested route and final path. Do not use a protected route sample when it redirected to /auth.

Field Core Web Vitals

The existing Sentry browser tracing integration captures real-user LCP, CLS, and INP. After deployment, use the first complete seven-day window as the field baseline and compare p75 by route and device class.

Targets follow the standard Core Web Vitals “good” thresholds:

Metric p75 target
LCP ≤ 2.5 s
CLS ≤ 0.1
INP ≤ 200 ms

Lab results explain reproducible loading behavior; Sentry p75 remains the release decision signal because it includes real devices, networks, authenticated routes, and actual interactions.