Skip to content

perf(clients): bring initial-load bundles back under the pre-Vite 8 size - #1466

Merged
iammukeshm merged 1 commit into
mainfrom
perf/react-bundle-tuning
Nov 4, 2026
Merged

iammukeshm merged 1 commit into
mainfrom
perf/react-bundle-tuning

Conversation

@iammukeshm

@iammukeshm iammukeshm commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Closes #1462.

The Vite 8 upgrade grew the initial load of both apps. Most of that wasn't new code. Rolldown split everything the entry loads up front into 45 (admin) and 68 (dashboard) small chunks, and each one compresses on its own. Concatenated, the same JS gzips about 20 KiB smaller. The dependency bumps added roughly 13 KiB on top.

What changed

  • Chunking (vite.config.ts, both apps). The entry's static import graph now goes into one vendor and one app chunk, using Rolldown's $initial tag in build.rolldownOptions.output.codeSplitting.groups. Route pages aren't $initial, so they stay lazy and are chunked as before. chunkSizeWarningLimit goes to 600 kB, because the ~560 kB minified vendor chunk is intentional.

  • Route translation catalogs (src/i18n.ts, src/routes.tsx, both apps). Only the namespaces the shell renders with ship in the main bundle:

    • dashboard: common, auth, notifications
    • admin: the same, plus nav and dashboard

    Every other en-US catalog is a small lazy chunk. lazyNamed(importer, name, namespaces) loads a route's namespaces in parallel with its page chunk, so the page doesn't render until its text is there. There's no flash and no extra round trip. The dashboard's command palette loads its own catalog alongside the dialog. pt-BR still loads as one chunk, as before.

  • Guard test (tests/i18n/route-namespaces.spec.ts, both apps). It walks the static import graph of every lazy route and fails when a page, or a component it imports, uses a namespace its route doesn't list. It also checks that the shell only uses eager namespaces. I removed files from the profile route to check that it fails, and it does, naming components/file/image-input.tsx.

  • Admin SignalR: now imported on first connect, the way the dashboard already does it. It was about 25 KiB gzip of the initial load, and the sign-in page doesn't need it.

  • Rules: .agents/rules/frontend/shared.md and the add-react-page skill now describe the eager and lazy catalog maps and the lazyNamed namespaces argument.

Numbers

Initial load is the entry script plus every modulepreload and stylesheet in dist/index.html, each file gzipped at level 9. All three columns were measured the same way. The pre-upgrade column is a build of 46939de3, the commit before #1461.

App Pre-upgrade (Vite 7) main This PR Files on main → this PR
admin 242.9 KiB 265.4 KiB 225.1 KiB 45 → 4
dashboard 245.2 KiB 271.3 KiB 229.9 KiB 68 → 4

The issue's 225.5 / 226.1 figures counted JS only. Measured that way, this PR is at 206.1 (admin) and 209.2 KiB (dashboard), against a pre-upgrade 224.4 and 225.2.

What didn't help:

Tested

  • Both apps: npm ci, npm run lint (0 errors; the warnings are the same as on main), tsc -b --noEmit clean, npm run build.
  • Playwright on the dev server:
    • admin 183/183
    • dashboard 251/251
    • new route-namespace specs: 29 (admin) and 43 (dashboard)
    • no [i18n] missing key warnings in either run
  • Playwright on the production build (vite preview):
    • dashboard 294/294
    • admin 209/212. The 3 failures are dev-only by design: format.spec imports /src/lib/format.ts from the dev server, and the demo-accounts dialog only renders in DEV.

Rolldown split the code each app's entry loads up front into 45 (admin) and
68 (dashboard) tiny chunks, and each one compresses on its own. Initial load
grew past what Rollup shipped.

- vite.config.ts: group everything the entry statically imports (Rolldown's
  $initial tag) into one vendor and one app chunk. Route pages stay lazy.
- i18n: only the shell's en-US catalogs ship in the main bundle. Route
  catalogs load with their route through lazyNamed(importer, name,
  namespaces), in parallel with the page chunk, so a page never renders
  before its text is there. The command palette loads its own catalog.
  A new tests/i18n/route-namespaces.spec.ts walks each route's import graph
  and fails when a route doesn't list a namespace its page uses.
- admin: import SignalR on first connect, like the dashboard already does.

Initial load (entry JS + CSS, gzip -9): admin 265.4 -> 225.1 KiB,
dashboard 271.3 -> 229.9 KiB. Pre-upgrade was 242.9 and 245.2 KiB.

Closes #1462
@iammukeshm
iammukeshm merged commit 82f10a6 into main Nov 4, 2026
18 checks passed
@iammukeshm
iammukeshm deleted the perf/react-bundle-tuning branch November 4, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tune the React bundles after the Vite 8 / Rolldown upgrade

1 participant