PERF: initial JS is 322 kB gzip before route code (budget 200 kB), +20–25% since 2026-09-27 #497

Open
opened 2026-09-30 08:04:41 +00:00 by kayg · 1 comment
Owner

Problem

Initial JavaScript is 1.6–2.0× the DESIGN §18 budget ("initial JS < 200 kB gzipped; plugins lazy-loaded") and grew 20–25% since the 2026-09-27 baseline. Cold loads at 4× CPU throttle miss the "cold first load < 1.0 s" budget on every desktop route.

Found by the #367 perf round (perf-test VM, release build of dev at 369ab6a2f, bench/run.sh --full, 5 interleaved runs per route, 4× CPU throttle).

Measurements

Route JS gzip CSS gzip Transfer vs 09-27 baseline LCP p50 / p95 @390 LCP p50 / p95 @1440
/today 336,656 B 59,545 B +21.7% 724 / 936 ms 1,380 / 1,636 ms
/files 347,695 B 61,469 B +20.9% 668 / 780 ms 1,136 / 1,364 ms
/photos 362,963 B 63,958 B +19.8% 764 / 888 ms 1,192 / 1,312 ms
/notes 329,061 B 59,352 B +22.0% 628 / 680 ms 1,048 / 1,152 ms
/search 326,043 B 58,679 B — 636 / 788 ms 1,104 / 1,128 ms
/settings/appearance 400,869 B 67,198 B +25.0% 688 / 984 ms 1,064 / 1,312 ms

Static import closure of the SvelteKit entry + app.js + root layout (nodes/0) from .vite/manifest.json: 321,748 B gzip in 59 files, before any route code. Largest parts:

Chunk gzip Notes
chunks/Br5mMnXy.js 135,312 B shared chunk; identifiers show Calendar, Photos, swatches and a city/timezone list (latitude/longitude ×423)
nodes/0 (root layout) 72,337 B
chunks/Dpg5AStj.js 23,191 B Search palette input handler (seen in palette long-animation-frame traces)

Browser soak (30 min, 1,915 full-page navigations at 4× throttle): 4,068 long tasks over 50 ms, maximum 2,175 ms. Each cold load parses the full closure.

Suggested direction (not decided)

Run a bundle visualizer on apps/web and move mode-specific code (Calendar, Photos, Settings pickers, city data) out of the root-layout closure behind dynamic imports; add the 200 kB gzip budget as a build-time report. Idle server RSS also rose from 148.4 to 185.2 MB versus the same baseline (route probe), which the job can check in passing.

Raw data: docs/perf/runs/2026-09-29T224501Z-369ab6a2/raw/routes.json.

## Problem Initial JavaScript is 1.6–2.0× the DESIGN §18 budget ("initial JS < 200 kB gzipped; plugins lazy-loaded") and grew 20–25% since the 2026-09-27 baseline. Cold loads at 4× CPU throttle miss the "cold first load < 1.0 s" budget on every desktop route. Found by the #367 perf round (perf-test VM, release build of `dev` at `369ab6a2f`, `bench/run.sh --full`, 5 interleaved runs per route, 4× CPU throttle). ## Measurements | Route | JS gzip | CSS gzip | Transfer vs 09-27 baseline | LCP p50 / p95 @390 | LCP p50 / p95 @1440 | |---|---:|---:|---:|---:|---:| | /today | 336,656 B | 59,545 B | +21.7% | 724 / 936 ms | 1,380 / 1,636 ms | | /files | 347,695 B | 61,469 B | +20.9% | 668 / 780 ms | 1,136 / 1,364 ms | | /photos | 362,963 B | 63,958 B | +19.8% | 764 / 888 ms | 1,192 / 1,312 ms | | /notes | 329,061 B | 59,352 B | +22.0% | 628 / 680 ms | 1,048 / 1,152 ms | | /search | 326,043 B | 58,679 B | — | 636 / 788 ms | 1,104 / 1,128 ms | | /settings/appearance | 400,869 B | 67,198 B | +25.0% | 688 / 984 ms | 1,064 / 1,312 ms | Static import closure of the SvelteKit entry + `app.js` + root layout (`nodes/0`) from `.vite/manifest.json`: **321,748 B gzip in 59 files**, before any route code. Largest parts: | Chunk | gzip | Notes | |---|---:|---| | `chunks/Br5mMnXy.js` | 135,312 B | shared chunk; identifiers show Calendar, Photos, swatches and a city/timezone list (`latitude`/`longitude` ×423) | | `nodes/0` (root layout) | 72,337 B | | | `chunks/Dpg5AStj.js` | 23,191 B | Search palette input handler (seen in palette long-animation-frame traces) | Browser soak (30 min, 1,915 full-page navigations at 4× throttle): 4,068 long tasks over 50 ms, maximum 2,175 ms. Each cold load parses the full closure. ## Suggested direction (not decided) Run a bundle visualizer on `apps/web` and move mode-specific code (Calendar, Photos, Settings pickers, city data) out of the root-layout closure behind dynamic imports; add the 200 kB gzip budget as a build-time report. Idle server RSS also rose from 148.4 to 185.2 MB versus the same baseline (route probe), which the job can check in passing. Raw data: `docs/perf/runs/2026-09-29T224501Z-369ab6a2/raw/routes.json`.
Author
Owner

#663 bundle/loading audit, production build of origin/dev c4a61e8cf0. Reused this issue after duplicate search; no product edits.

The static entry + root layout + root error closure is now 369,672 B separately gzipped JS (1,091,341 B raw), 60,533 B gzip CSS, across 77 JS files. Required route closures: Files 394,095 B JS, Calendar view 389,945 B, Photos 408,334 B, Mail 380,528 B, Settings 452,529 B, Note editor 612,891 B and public link 606,717 B. These are build graph counts, not current browser ready-time samples; optional runtime imports are separate.

Largest initial shared chunk _app/immutable/chunks/CvL5jGdk.js is 150,468 B gzip. It contains 423 latitude/longitude records, plus shared Calendar/viewer code. Root layout is another 82,845 B. The concrete import roots are +layout.svelte:20–22 (sidebar, Search and shortcut card), app-sidebar.svelte:51–66 (all Tab sidebars), and appearance/autoScheme.svelte.ts:9 (timezoneCity's full map). Lazy-load optional surfaces/data while keeping shortcut matching synchronous and one source of truth (#556).

PublicLinkPage.svelte:36 statically imports NoteEditorSurface; its :603 use is conditional on the guest editor. Thus even a file gallery's route graph imports the 213,285 B gzip editor chunk. Move that guest editor behind an actual use boundary; coordinate with view-only public links in DESIGN §54. NoteView.svelte:33 statically imports the editor for the real Note route, which is expected; reduce its first-use schema/UI cost without data-contract changes.

Optional paths correctly stay out of the shared closure: BklitAnalytics/React 163,744 B gzip, HLS 155,087 B, pdf.js 120,340 B. Highlight.js has three locked versions (UI 11.11.1, lowlight 11.11.2, top-level 11.12.0). UI TextView has its own dynamic core/grammar imports; the editor uses lowlight plus its optional syntaxLanguages chunk. This is a deduplication candidate, not evidence that all three cores are initially downloaded. Lockfile duplication alone must not be counted as initial payload.

Round 7a retains these import roots; round 7b blaze-settings retains static section imports and fix-499 retains the Search/QuickLook import roots. Its cache/snapshot/mutation work changes data handling, not these boundaries. Full findings and all route sizes are in audit-findings.md on job/perf-arch-bundle; analyzer is bench/bundle-audit.mjs. Compression/cache follow-ups are #803/#804 and PDF readiness is #805. No new duplicate bundle issue.

#663 bundle/loading audit, production build of origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Reused this issue after duplicate search; no product edits. The static entry + root layout + root error closure is now 369,672 B separately gzipped JS (1,091,341 B raw), 60,533 B gzip CSS, across 77 JS files. Required route closures: Files 394,095 B JS, Calendar view 389,945 B, Photos 408,334 B, Mail 380,528 B, Settings 452,529 B, Note editor 612,891 B and public link 606,717 B. These are build graph counts, not current browser ready-time samples; optional runtime imports are separate. Largest initial shared chunk _app/immutable/chunks/CvL5jGdk.js is 150,468 B gzip. It contains 423 latitude/longitude records, plus shared Calendar/viewer code. Root layout is another 82,845 B. The concrete import roots are +layout.svelte:20–22 (sidebar, Search and shortcut card), app-sidebar.svelte:51–66 (all Tab sidebars), and appearance/autoScheme.svelte.ts:9 (timezoneCity's full map). Lazy-load optional surfaces/data while keeping shortcut matching synchronous and one source of truth (#556). PublicLinkPage.svelte:36 statically imports NoteEditorSurface; its :603 use is conditional on the guest editor. Thus even a file gallery's route graph imports the 213,285 B gzip editor chunk. Move that guest editor behind an actual use boundary; coordinate with view-only public links in DESIGN §54. NoteView.svelte:33 statically imports the editor for the real Note route, which is expected; reduce its first-use schema/UI cost without data-contract changes. Optional paths correctly stay out of the shared closure: BklitAnalytics/React 163,744 B gzip, HLS 155,087 B, pdf.js 120,340 B. Highlight.js has three locked versions (UI 11.11.1, lowlight 11.11.2, top-level 11.12.0). UI TextView has its own dynamic core/grammar imports; the editor uses lowlight plus its optional syntaxLanguages chunk. This is a deduplication candidate, not evidence that all three cores are initially downloaded. Lockfile duplication alone must not be counted as initial payload. Round 7a retains these import roots; round 7b blaze-settings retains static section imports and fix-499 retains the Search/QuickLook import roots. Its cache/snapshot/mutation work changes data handling, not these boundaries. Full findings and all route sizes are in audit-findings.md on job/perf-arch-bundle; analyzer is bench/bundle-audit.mjs. Compression/cache follow-ups are #803/#804 and PDF readiness is #805. No new duplicate bundle issue.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#497
No description provided.