118 lines
4.6 KiB
Markdown
118 lines
4.6 KiB
Markdown
# Performance baseline
|
||
|
||
Baseline date: 2026-07-15
|
||
|
||
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:
|
||
|
||
```bash
|
||
pnpm perf:bundle
|
||
pnpm perf:bundle:check
|
||
```
|
||
|
||
`perf:bundle` generates the report without failing. `perf:bundle:check` also
|
||
enforces the eager client JavaScript budgets used by CI.
|
||
|
||
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 | Direct-import baseline | Current eager baseline | Change | Current async baseline |
|
||
| --- | ---: | ---: | ---: | ---: |
|
||
| `/splash` | 646.2 KiB | 599.7 KiB | -46.5 KiB | 49.2 KiB |
|
||
| `/chat` | 669.8 KiB | 637.8 KiB | -32.0 KiB | 49.6 KiB |
|
||
| `/subscription` | 658.4 KiB | 616.9 KiB | -41.5 KiB | 20.6 KiB |
|
||
| `/tip` | 651.6 KiB | 610.9 KiB | -40.7 KiB | 20.6 KiB |
|
||
|
||
The earlier direct-import pass replaced root `@/utils` and `@/data/storage`
|
||
barrel imports with symbol-level module paths. The current Client-boundary pass
|
||
separates the Payment → User and Payment → Chat synchronizers, uses direct
|
||
provider imports, and loads the Dexie-backed chat repository through a dynamic
|
||
import. UA Parser remains an intentional eager dependency because the app still
|
||
requires its general browser, OS, and device detection behavior.
|
||
|
||
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 |
|
||
| `ua-parser-js` | eager | eager (retained intentionally) |
|
||
| Dexie | eager on all baseline routes | async on `/splash` and `/chat`; absent on `/subscription` and `/tip` |
|
||
|
||
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.
|
||
|
||
The initial budgets are intentionally above the baseline and are enforced by CI:
|
||
|
||
| Route | Eager client JS budget |
|
||
| --- | ---: |
|
||
| `/splash` | 605 KiB |
|
||
| `/chat` | 645 KiB |
|
||
| `/subscription` | 625 KiB |
|
||
| `/tip` | 620 KiB |
|
||
|
||
## Lab Web Vitals baseline
|
||
|
||
Build and measure the local production app:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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.
|