Skip to content

perf(web): trim non-crypto weight and boot requests from the entry chunk - #14667

Merged
dylanjeffers merged 12 commits into
mainfrom
perf/web-entry-noncrypto
Oct 13, 2026
Merged

dylanjeffers merged 12 commits into
mainfrom
perf/web-entry-noncrypto

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

The non-crypto half of the web bundle audit. The crypto half is #14660–#14664.

Impact (web entry chunk, prod build)

min gzip
main 5,627 KB 1,526 KB
this PR, entry 3,763 KB 1,113 KB
this PR, entry + new vendor chunk 4,103 KB 1,222 KB

Changes (one commit each)

  1. Lazy Optimizely. @optimizely/optimizely-sdk loads inside createOptimizelyClient (~357 KB raw).
  2. Lazy queue popover. It loads on first open and is prefetched on hover, which takes the atlaskit drag-and-drop library out of the entry (~225 KB raw).
  3. Lazy notification panel and mobile-web notification page. The panel mounts on the first hover or open of the bell (~105 KB raw). The notification list is no longer fetched at boot.
  4. SubPage enum moved to settings-page/types.ts. WebPlayer imported it from the mobile SettingsPage and dragged in the whole page (~78 KB raw).
  5. redux-persist uses localStorage instead of localforage (~96 KB raw). Each key is copied once from localforage, behind a per-key done flag, and localforage is dynamically imported only for that copy. Search history and library filters carry over; tested by seeding IndexedDB.
  6. SDK: tus-js-client and Node file-type load on demand (~209 KB raw). Uploads also stop if aborted while tus loads. A changeset is included.
  7. SDK: the storage node list is fetched on first getSelectedNode() instead of in the constructor. That's one fewer request on every web and mobile boot. Concurrent callers share the request, and a failed request is retried.
  8. Trending fetches only the visible lineup (desktop and mobile web). The other time ranges load on hover or press of their filter, and Underground when its tab opens. 4 fewer requests on load.
  9. The 25 remaining NiceModals use registerLazyModal, appended as a separate block. Expect a small conflict with perf: load Solana, wallet and purchase code on demand #14664 in registerNiceModals.ts: drop the side-effect imports and keep both lazy blocks.
  10. svgo for SVGs. It uses mobile's preset and keeps ids, colors, viewBox and geometry. The icon SVGs in the entry go from about 385 to 247 KB. Pixel-diffing all 279 SVGs at 8x showed only sub-pixel edge changes. Looser settings visibly moved logos with embedded images, so they're off.
  11. Vendor chunk for react, react-dom, react-router, redux, react-redux, redux-saga and query-core. It stays cached across deploys and is modulepreloaded. SSR is unchanged.
  12. Preconnect to the API host and experiments.audius.co.

Test plan

  • tsc: web, sdk, mobile. Web vitest (36 files), sdk vitest (21 files), with new tests for the storage migration and the lazy storage node fetch.
  • Prod build smoke test signed out: no app errors; the queue popover, trending tabs and the Share modal work; no /health_check on load; only the week trending request on load.
  • Signed in: notification panel (bell hover/open, ?openNotifications), the mobile-web notifications page, mobile-web settings sub-pages
  • Search history and library filters survive the upgrade (localforage → localStorage)
  • Track and image upload on web and mobile (lazy tus, lazy storage node selection)
  • Open a sample of lazy modals
  • Feature flags still apply

🤖 Generated with Claude Code

dylanjeffers and others added 12 commits October 12, 2026 16:59
Only createOptimizelyClient needs it, so import it there and drop it from
the entry chunk. common's import becomes type-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The popover pulls in the drag-and-drop library and only renders after the
queue button is clicked. Load it on first open, prefetching on hover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The desktop panel now mounts on first hover or open of the bell, which
also kicks off the notifications fetch. The mobile-web notification page
becomes a lazy route like its neighbours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WebPlayer only needs the enum, but importing it from the mobile settings
page pulled the whole page into the entry chunk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Search history and library filters are tiny, so localStorage is enough.
Existing values are copied from localforage on first read of each key;
localforage is only imported for that one-time copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both are only needed during uploads, but were evaluated at startup on web
and mobile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StorageNodeSelector hit /health_check in its constructor, so every app boot
made the request even though only uploads use the result. Fetch it the first
time a node is selected, sharing one request across concurrent callers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trending fired week, month, all-time and underground queries on load while
showing one of them. Fetch the visible lineup, and start the others when
their filter is hovered (desktop) or pressed (mobile web) so switching
stays quick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Convert the non-crypto NiceModal registrations (share, add to collection,
confirmations, contest, welcome, chat blast, etc.) to registerLazyModal so
none of them load until opened. Registration moves out of the modal modules
so the lazy import can't re-register an open modal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web's svgr had no svgo pass, so icons shipped as raw Figma exports. Use the
same preset mobile runs on these files, keeping viewBox, ids and colors as
they are and skipping the lossy curve and transform rewrites. Sources shrink
from 794 KB to 546 KB; a pixel diff of all 279 SVGs at 8x shows only
sub-pixel edge changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first API fetches and the Optimizely datafile script both wait on a fresh
connection. The API link is crossorigin because fetch uses CORS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
React, react-dom, react-router, redux, react-redux, redux-saga and
query-core change rarely, so a separate chunk stays cached across deploys.
The chunk has no imports of its own and is modulepreloaded from index.html.
SSR keeps its default chunking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7b0022e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@audius/sdk Patch
@audius/sdk-legacy Patch
@audius/protocol-dashboard Patch
@audius/sp-actions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@dylanjeffers
dylanjeffers merged commit 4042e05 into main Oct 13, 2026
24 checks passed
@dylanjeffers
dylanjeffers deleted the perf/web-entry-noncrypto branch October 13, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant