koala73 / koala73/worldmonitor
perf(pwa): SW globPatterns still precaches every app chunk — 882KB/74 requests of unused lazy chunks (residual of #4578)
- Dominant language
- TypeScript
- Stars
- 86.6k
- Forks
- 13.1k
- Avg merge
- 8h 4m
- Merged PRs (30d)
- 825
Description
## Problem
`globPatterns` still precaches **every** `.js`/`.css` the build emits, including in-app lazy chunks the dashboard never loads. On the DebugBear mobile profile (US East, 1.6 Mbps, 150 ms RTT, slow device, Chrome 147, 2026-07-18 07:05 UTC) that is **74 requests / 882 KB of net-new transfer — 24.2 % of the page's 3,638 KB** — for code this page did not execute.
This is the **residual of #4578**. That issue was closed by narrowing `globIgnores` to exclude *off-page/public* directories (`pro/**`, `favico/**`, `textures/**`, later `blog/**` via #4899). The **glob itself was never narrowed**, so the app's own `/assets/*` chunks were never in scope of that fix and have been precached the whole time.
## Root cause
`vite.config.ts:1013` (origin/main `c45acd49c`):
```js
globPatterns: ['**/*.{js,css,ico,png,svg,woff2}'],
globIgnores: [
'**/ml*.js', '**/onnx*.wasm', '**/locale-*.js', '**/clerk-*.js',
'**/*.woff2',
'pro/**', 'favico/**', 'textures/**', // #4578
'blog/**', // #4891 / #4899
],
```
Every entry in `globIgnores` is a *directory or filename* exclusion. Nothing excludes `assets/**`, so each new code-split chunk **auto-joins the precache set at build time**. The config has not changed since #5220 (2026-07-11) — the wave grew because the app did (10-widget MCP fleet #5199, China coverage, procurement #5266, per-domain rpc-clients).
That makes this a **silently compounding** regression: every feature that adds a chunk adds first-visit bandwidth, with no signal at review time.
## Evidence
Source: DebugBear analysis **[83831737](https://www.debugbear.com/viewResult/83831737)** (project `103025`, page `693052`) — `www.worldmonitor.app/dashboard`, 303 requests, test config **US East, 1.6 Mbps, 150 ms RTT, slow device, Chrome 147, Lighthouse 13.4.0**, run **2026-07-18 07:05 UTC**. Day-over-day comparison baseline is **[83766301](https://www.debugbear.com/viewResult/83766301)** (2026-07-17 07:03 UTC).
> Trend figures (29 samples, 2026-06-30 → 2026-07-18) come from the series export for the same page; each sample carries its own `analysis.id`, so any row is re-openable at `debugbear.com/viewResult/`.
Largest net-new precache items, none executed by this page:
```
277.7 KB maplibre-DfDOI80j.js ← entire map engine
78.7 KB main-BU6Lo6Ic.css
39.9 KB panels-economy-CIhfp2uk.js
36.1 KB panels-markets-Di7EvD6u.js
31.0 KB panels-risk-BToxMI9N.js
26.0 KB search-manager-BbOE8F4U.js
25.8 KB panels-energy-BJU2dFhz.js
20.8 KB panels-defense-Dziz3eM6.js
18.3 KB oref-locations-EKm8gxKt.js
17.6 KB protomaps-CrcXoKbV.js
15.1 KB satellite.es-DES7etDd.js
11.3 KB maplibre-B46vZ9Yf.css
```
`maplibre` alone is 277.7 KB — and the map is demand-gated on mobile, the same reasoning that got `textures/**` excluded in #4578.
Verified **not** double-downloads: of 64 URLs requested more than once, 63 second-occurrences transfer 0 bytes (HTTP cache hit); only 17 KB is true re-download. The waste is unused chunks, not redundancy.
## Correlated lab signal
DebugBear lab Speed Index shows a clean step change with **zero distribution overlap**, consistent with a growing precache wave competing for bandwidth inside the visual-completion window:
| window | n | range | mean |
|---|---|---|---|
| pre 2026-07-10 | 19 | 3,975–5,333 ms | 4,530 ms |
| post 2026-07-10 | 10 | 5,386–6,585 ms | **5,970 ms (+23 %)** |
Deliberate CLS/deferral trades in that window (#5205, #5161, #5220, #5167, later #5333/#5344) account for part of it and bought a real field win — CrUX CLS p75 0.18 → 0.09, LCP p75 3,761 → 1,503 ms. The precache growth is the part that is pure loss.
## Proposed fix
Narrow `globPatterns` to the true app shell and let `runtimeCaching` handle the rest on demand:
- Precache: entry chunk, its static imports, the shell CSS, `manifest.webmanifest`, the small root favicons already in `includeAssets`.
- Do **not** precache: lazy panel chunks (`panels-*`), map engine (`maplibre*`, `protomaps*`), per-domain `rpc-client-*`, demand-gated data chunks.
- Add a `StaleWhileRevalidate` runtime-cache route for `/assets/*` so offline/repeat-visit behaviour for lazy chunks is preserved without first-visit cost.
Guard against silent regrowth: assert a **budget on the generated precache manifest** (entry count + total bytes) in the existing deploy-config test surface (`tests/deploy-config.test.mjs`), so a new chunk joining the precache set fails CI instead of quietly costing first-visit bandwidth. Without this the issue recurs a third time.
## Scope
- Residual of #4578 (closed 2026-07-01 — fixed off-page assets only, never `assets/**`). Not a dup: different asset class, different fix.
- Independent of #5372, which defers SW registration: that one changes *when* the wave runs, this one changes *what* is in it. Doing only one leaves most of the cost.
- Related: #5165 — its "wave 2" panel chunks are SW precache fetches, not deferred panel loads (evidence in #5372).
- Related: #4487 (parent CWV epic that #5165 sits under). Filed as a sibling rather than a child: #4487 tracks *field* Core Web Vitals, and this is first-visit bandwidth showing up in lab Speed Index / TTI — reparent if you'd rather track it there.
## Acceptance
- Generated precache manifest entry count and total bytes both drop; recorded before/after.
- `maplibre*`, `panels-*`, and `rpc-client-*` absent from the precache manifest.
- Fresh-profile waterfall shows no `/assets/*` `fetch` for chunks the page did not execute.
- Offline repeat visit still works for the shell; lazy chunks resolve from runtime cache.
- CI budget guard fails when a new chunk enters the precache set.
- Re-run the DebugBear mobile profile; record Speed Index, Visually Complete, TTI, total transfer. Read against **CV 28 %** run-to-run TBT noise (2,969–8,756 ms over 29 samples) — single-run TBT deltas are not evidence.
Contributor guide
Research direction
Start with vite.config.ts around line 1013 and inspect how the generated precache manifest is produced. Then read tests/deploy-config.test.mjs and run its deploy-config checks. Done means the manifest excludes maplibre*, panels-* and rpc-client-* chunks, stays within a guarded entry/byte budget, and preserves shell offline support with lazy chunks handled by runtime caching.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, vite
- Domain
- build-system, performance, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100