perf(web): halve the cold-start bundle by splitting Clerk and cold routes - #9058
Conversation
There was a problem hiding this comment.
One finding: the new top-level Suspense boundary makes the first React commit paint nothing, which tears down the index.html boot splash on cloud cold starts. Details inline on apps/web/src/main.tsx.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 308df80. Configure here.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This change substantially rewires production startup and authentication loading across browser and Electron clients, while also adding global route-splitting and chunk-recovery behavior. The modified authentication directory and cross-cutting first-paint/error-path changes warrant focused human review. You can add or adjust custom eligibility rules. Learn more. |
…utes The web entry statically imported the Electron Clerk provider, which bundles the full clerk-js runtime, so every client shipped ~565 KB gzip of Clerk it might never run. The generated route tree also eagerly imported settings, pull-request, and usage code, and the root route pinned the theme editor and settings nav into the entry graph. Managed auth now lazy-loads only the selected Clerk runtime behind the existing cloud-config gate, route components split into chunks fetched on navigation intent, and the theme editor and settings sidebar nav load behind their existing runtime conditionals. Measured initial payload drops from 2.15 MB to 0.89 MB gzip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lazy managed-auth boundary rendered a null Suspense fallback, so React's first commit cleared the index.html boot shell and left a blank window while the auth chunk (and, with route splitting, the initial route chunk) downloaded. Resolve the selected auth runtime and the initial route load before calling render so the splash holds until real UI paints. Uses a .then chain rather than top-level await, which fragments the entry graph into ~100 extra chunks under rolldown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A deploy or desktop server swap between page load and a lazy chunk fetch can 404 the old hashed assets, stranding the client with no recovery path now that startup depends on split chunks. Handle Vite's preloadError event with a single sessionStorage-guarded reload so a fresh index.html is picked up, while a persistent failure still surfaces instead of looping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A failed startup chunk fetch after the one guarded reload left the boot splash up forever, and a successful reload after a route chunk failure still cleared the guard, which let a persistent failure loop. Move the guard into a tested helper, skip the paint when a reload is scheduled, only re-arm the guard after a boot that fetched every chunk it asked for, and replace the splash with a short message when the auth shell chunk fails for good. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
860c5e8 to
0bbaa39
Compare
## What's Changed * perf(client-runtime): keep turn and checkpoint refs stable while streaming by @t3dotgg in pingdotgg/t3code#9145 * perf(clients): lease sidebar status by visibility by @StiensWout in pingdotgg/t3code#9052 * fix(desktop): show newest changes in nightly previews by @t3dotgg in pingdotgg/t3code#9138 * fix(settings): sync auto-settle and other shared preferences across environments by @t3dotgg in pingdotgg/t3code#9147 * fix(server): prevent accidental service downgrades by @t3dotgg in pingdotgg/t3code#5302 * fix(server): keep attachments until the command commits by @t3dotgg in pingdotgg/t3code#7941 * fix(claude): preview images read from the workspace by @t3dotgg in pingdotgg/t3code#9119 * fix(web): keep generated muted foreground dimmer than entered text by @flamboh in pingdotgg/t3code#9113 * fix(clients): stop repeating expanded commands by @t3dotgg in pingdotgg/t3code#9120 * fix(grok): health check, model selection, and stop all work against the real CLI by @t3dotgg in pingdotgg/t3code#9154 * perf(web): halve the cold-start bundle by splitting Clerk and cold routes by @StiensWout in pingdotgg/t3code#9058 * feat(desktop): update the desktop app on remote Macs from the Update button by @t3dotgg in pingdotgg/t3code#6554 * test(server): measure shell, second client, and reconnect transfer by @t3dotgg in pingdotgg/t3code#9157 * fix(web): project default model works on the hosted app by @juliusmarminge in pingdotgg/t3code#9142 * fix(web): darken neutral control surfaces by @maria-rcks in pingdotgg/t3code#9064 * fix(web): preserve panel state across workspace refreshes by @maria-rcks in pingdotgg/t3code#8968 * feat(files): open markdown, HTML, and PDF files outside the workspace by @juliusmarminge in pingdotgg/t3code#9140 * feat(web): render HTML and PDF files in the file viewer by @juliusmarminge in pingdotgg/t3code#9143 * fix(web): compact project settings actions by @maria-rcks in pingdotgg/t3code#9160 * fix(web): browse folders from file breadcrumbs by @404khai in pingdotgg/t3code#8910 ## New Contributors * @404khai made their first contribution in pingdotgg/t3code#8910 **Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260902.1252...v0.0.39-nightly.20260902.1253 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260902.1253

Every web and desktop client downloads ~2.15 MB gzip of JavaScript before first paint, and ~565 KB of that is the bundled clerk-js runtime that local-mode users never run: the entry statically imports the Electron Clerk provider (which bundles clerk-js) even in the browser, where Clerk hotloads its runtime from CDN anyway. The generated route tree also eagerly pulls settings, pull-request, and usage code into the entry, and the root route pins the theme editor and settings sidebar nav there too.
This splits the cold-start graph along its existing runtime conditionals:
clerkPublishableKey && hasCloudPublicConfig()gate. Local mode downloads no Clerk at all; the browser build no longer bundles clerk-js.autoCodeSplittingon the TanStack Router plugin), prefetched on navigation intent (defaultPreload: "intent") so first settings/PR navigation does not pay the load.Measured on a production build (gzip -6, entry + modulepreloads + CSS from index.html): 2.15 MB → 0.89 MB (−59%). The Electron Clerk runtime is now a 555 KB chunk loaded only in the desktop shell with cloud config; only the small
@clerk/react/@clerk/sharedhook layer (~86 KB) remains entry-resident for always-mounted consumers. Desktop chunk loading goes through the existingt3code://protocol proxy, which forwards arbitrary/assets/*paths, so split chunks resolve the same way the entry does.Verified with the web unit tests around the touched areas (routes titlebar, Clerk profile pages, Electron passkeys, managed auth, AppRoot), targeted lint, and a web typecheck, plus the before/after bundle measurement above.
Change authored by Claude Fable 5 running in Claude Code.
🤖 Generated with Claude Code
Note
Medium Risk
Startup and auth wiring changed (deferred render, lazy Clerk shells, chunk-load reload), so regressions could affect first paint, cloud login, or behavior after deploys—though local mode and error paths are explicitly handled.
Overview
Cuts cold-start JavaScript by splitting the entry graph (~2.15 MB gzip → ~0.89 MB in the PR’s measurement) by moving Clerk, route pages, and a few always-mounted-but-rarely-used UI behind lazy chunks instead of static imports.
Managed auth no longer loads at startup:
main.tsxdynamically importsBrowserManagedAuthShellorElectronManagedAuthShellonly when cloud config and a Clerk publishable key are present, so local mode skips Clerk entirely and the browser build avoids bundling the Electronclerk-jspayload on the critical path.Route code is code-split via TanStack Router
autoCodeSplittingin Vite, withdefaultPreload: "intent"so settings/PR-style routes prefetch on hover/focus rather than on first paint. Settings sidebar nav and theme editor panel areReact.lazy+Suspensewhere they only mount when settings or an editor session is active.Boot behavior changes: first paint waits on
router.load()and the optional auth shell chunk so the HTML boot splash stays visible until real UI can render;vite:preloadErrortriggers a one-shot reload (chunkReloadGuard) when stale hashed assets 404 after deploy, with tests for the guard.Reviewed by Cursor Bugbot for commit 0bbaa39. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Split Clerk and cold routes to halve web cold-start bundle
Suspenseboundary with an empty fallbackMacroscope summarized 0bbaa39.