Skip to content

perf(web): halve the cold-start bundle by splitting Clerk and cold routes - #9058

Merged
t3dotgg merged 4 commits into
pingdotgg:mainfrom
StiensWout:t3code/split-cold-start-graph
Sep 2, 2026
Merged

perf(web): halve the cold-start bundle by splitting Clerk and cold routes#9058
t3dotgg merged 4 commits into
pingdotgg:mainfrom
StiensWout:t3code/split-cold-start-graph

Conversation

@StiensWout

@StiensWout StiensWout commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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:

  • Managed auth lazy-loads only the selected Clerk runtime (browser or Electron shell) behind the existing clerkPublishableKey && hasCloudPublicConfig() gate. Local mode downloads no Clerk at all; the browser build no longer bundles clerk-js.
  • Route components become split chunks (autoCodeSplitting on the TanStack Router plugin), prefetched on navigation intent (defaultPreload: "intent") so first settings/PR navigation does not pay the load.
  • The theme editor panel and settings sidebar nav lazy-load behind their existing "only renders when active" conditions, since the root route keeps them out of route splitting's reach.

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/shared hook layer (~86 KB) remains entry-resident for always-mounted consumers. Desktop chunk loading goes through the existing t3code:// 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.tsx dynamically imports BrowserManagedAuthShell or ElectronManagedAuthShell only when cloud config and a Clerk publishable key are present, so local mode skips Clerk entirely and the browser build avoids bundling the Electron clerk-js payload on the critical path.

Route code is code-split via TanStack Router autoCodeSplitting in Vite, with defaultPreload: "intent" so settings/PR-style routes prefetch on hover/focus rather than on first paint. Settings sidebar nav and theme editor panel are React.lazy + Suspense where 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:preloadError triggers 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

  • Moves Clerk browser and Electron provider implementations into separate dynamically loaded auth-shell modules; startup loads only the one selected by the environment, or neither when cloud config is absent
  • Enables TanStack Router automatic route code splitting in vite.config.ts and configures the router to preload route chunks on hover/focus intent
  • Lazily loads the settings navigation in AppSidebarLayout.tsx and the theme editor panel in ThemeEditorHost.tsx, each wrapped in a Suspense boundary with an empty fallback
  • Adds a chunk-reload guard in chunkReloadGuard.ts that reloads once on the first split-chunk load failure using a session-storage marker, and surfaces subsequent failures without reloading
  • Startup in main.tsx now awaits the selected auth shell and router before the first React render, and writes a load-failure message to the boot shell if startup rejects without a scheduled reload
  • Risk: if a user's session storage is blocked, the chunk-reload guard will not attempt a reload and the raw chunk-load error surfaces to the boot shell instead

Macroscope summarized 0bbaa39.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 1, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread apps/web/src/main.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/web/src/main.tsx Outdated
Comment thread apps/web/src/main.tsx
@macroscopeapp

macroscopeapp Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

StiensWout and others added 4 commits September 1, 2026 18:32
…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>
@t3dotgg
t3dotgg force-pushed the t3code/split-cold-start-graph branch from 860c5e8 to 0bbaa39 Compare September 2, 2026 01:33
@t3dotgg
t3dotgg merged commit 80c708a into pingdotgg:main Sep 2, 2026
27 checks passed
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 2, 2026
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants