Skip to content

fix(clerk-js,ui): Preload component chunks in parallel during mount#7901

Merged
jacekradko merged 6 commits intomainfrom
jacek/user-4747-ui-delay-opening-user-menu-and-org-switcher-on-slow-internet
Feb 24, 2026
Merged

fix(clerk-js,ui): Preload component chunks in parallel during mount#7901
jacekradko merged 6 commits intomainfrom
jacek/user-4747-ui-delay-opening-user-menu-and-org-switcher-on-slow-internet

Conversation

@jacekradko
Copy link
Copy Markdown
Member

@jacekradko jacekradko commented Feb 21, 2026

Summary

  • Move preloadComponent() outside the first-call guard in ensureMounted() so it fires on every call, not just the first
  • Pass preloadHint from all 16 mount* methods to ensureMounted()

Resolves USER-4747

Testing PR: https://github.com/clerk/dashboard/pull/8496

How it works

On initial mount (first call to ensureMounted), preloading already worked — preloadComponent() fired inside the if (!componentsControlsResolver) block before the common chunk import, so the component chunk and common chunk downloaded in parallel.

The bug was on subsequent mounts. When a different component was already mounted (e.g. <SignIn /> mounts first, then later <UserButton /> mounts), componentsControlsResolver is already defined, so the entire if (!componentsControlsResolver) block — including preloadComponent() — was skipped. The flow became:

  1. ensureMounted() resolves immediately (common chunk already loaded)
  2. mountComponent() triggers React render
  3. React encounters the lazy() component → only then starts downloading the component chunk

By moving the preload call outside the guard, every mount*() call immediately starts downloading its component chunk, even when the Components renderer is already initialized.

preloadComponent is idempotent (webpack/rspack caches the import promise), so repeated calls are safe.

Before (subsequent mount)

ensureMounted() → componentsControlsResolver exists, skip block entirely → mountComponent() → React render → React.lazy() triggers chunk download

After (subsequent mount)

ensureMounted() → preloadComponent() starts chunk download immediately → mountComponent() → React render → React.lazy() finds module already loaded
Screenshot 2026-02-23 at 10 39 33 AM

Test plan

  • pnpm build passes
  • pnpm test passes
  • git diff main --stat shows only intended changes

Summary by CodeRabbit

  • Bug Fixes
    • Optimized component loading to reduce first-render latency on slow network connections by parallelizing component chunk preloading during initialization.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Feb 24, 2026 3:14pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 21, 2026

🦋 Changeset detected

Latest commit: ac04f66

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

This PR includes changesets to release 4 packages
Name Type
@clerk/clerk-js Patch
@clerk/ui Patch
@clerk/chrome-extension Patch
@clerk/expo 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

@jacekradko
Copy link
Copy Markdown
Member Author

!snapshot

@clerk-cookie
Copy link
Copy Markdown
Collaborator

Hey @jacekradko - the snapshot version command generated the following package versions:

Package Version
@clerk/agent-toolkit 0.3.0-snapshot.v20260221025222
@clerk/astro 3.0.0-snapshot.v20260221025222
@clerk/backend 3.0.0-snapshot.v20260221025222
@clerk/chrome-extension 3.0.0-snapshot.v20260221025222
@clerk/clerk-js 6.0.0-snapshot.v20260221025222
@clerk/dev-cli 1.0.0-snapshot.v20260221025222
@clerk/expo 3.0.0-snapshot.v20260221025222
@clerk/expo-passkeys 1.0.0-snapshot.v20260221025222
@clerk/express 2.0.0-snapshot.v20260221025222
@clerk/fastify 2.7.0-snapshot.v20260221025222
@clerk/localizations 4.0.0-snapshot.v20260221025222
@clerk/msw 0.0.1-snapshot.v20260221025222
@clerk/nextjs 7.0.0-snapshot.v20260221025222
@clerk/nuxt 2.0.0-snapshot.v20260221025222
@clerk/react 6.0.0-snapshot.v20260221025222
@clerk/react-router 3.0.0-snapshot.v20260221025222
@clerk/shared 4.0.0-snapshot.v20260221025222
@clerk/tanstack-react-start 1.0.0-snapshot.v20260221025222
@clerk/testing 2.0.0-snapshot.v20260221025222
@clerk/ui 1.0.0-snapshot.v20260221025222
@clerk/upgrade 2.0.0-snapshot.v20260221025222
@clerk/vue 2.0.0-snapshot.v20260221025222

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/agent-toolkit

npm i @clerk/agent-toolkit@0.3.0-snapshot.v20260221025222 --save-exact

@clerk/astro

npm i @clerk/astro@3.0.0-snapshot.v20260221025222 --save-exact

@clerk/backend

npm i @clerk/backend@3.0.0-snapshot.v20260221025222 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.0.0-snapshot.v20260221025222 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.0.0-snapshot.v20260221025222 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@1.0.0-snapshot.v20260221025222 --save-exact

@clerk/expo

npm i @clerk/expo@3.0.0-snapshot.v20260221025222 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.0-snapshot.v20260221025222 --save-exact

@clerk/express

npm i @clerk/express@2.0.0-snapshot.v20260221025222 --save-exact

@clerk/fastify

npm i @clerk/fastify@2.7.0-snapshot.v20260221025222 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.0.0-snapshot.v20260221025222 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.1-snapshot.v20260221025222 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.0.0-snapshot.v20260221025222 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.0.0-snapshot.v20260221025222 --save-exact

@clerk/react

npm i @clerk/react@6.0.0-snapshot.v20260221025222 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.0.0-snapshot.v20260221025222 --save-exact

@clerk/shared

npm i @clerk/shared@4.0.0-snapshot.v20260221025222 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.0.0-snapshot.v20260221025222 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.0-snapshot.v20260221025222 --save-exact

@clerk/ui

npm i @clerk/ui@1.0.0-snapshot.v20260221025222 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.0-snapshot.v20260221025222 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.0-snapshot.v20260221025222 --save-exact

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Feb 21, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7901

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7901

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7901

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7901

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7901

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7901

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7901

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7901

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7901

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7901

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@7901

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7901

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7901

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7901

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7901

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7901

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7901

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7901

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7901

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7901

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7901

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7901

commit: ac04f66

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c5bd5b2 and ac04f66.

📒 Files selected for processing (2)
  • packages/clerk-js/src/core/clerk.ts
  • packages/ui/src/Components.tsx

📝 Walkthrough

Walkthrough

This pull request adds a preloadHint parameter to component mounting calls across the Clerk codebase. In packages/clerk-js, multiple ui.ensureMounted() invocations are updated to include a preloadHint payload specifying component names. In packages/ui, the mountComponentRenderer function is modified to preload the specified component chunks earlier in the mounting sequence, before initializing mount controls. The preloading mechanism is idempotent, returning a cached promise on subsequent calls. A changeset documents patch-level version bumps for both @clerk/clerk-js and @clerk/ui packages, reflecting this runtime optimization for reducing first-render latency on slower connections.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: preloading component chunks in parallel during mount to optimize performance on slow connections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Replace the keepMounted Popover approach with chunk preloading.
Pass preloadHint to ensureMounted() from all mount methods so component
chunks download in parallel with the common chunk, eliminating the
sequential waterfall on slow connections. Move preloadComponent() call
outside the first-call guard so it fires on every ensureMounted() call.
@jacekradko jacekradko force-pushed the jacek/user-4747-ui-delay-opening-user-menu-and-org-switcher-on-slow-internet branch from 9b06f86 to cdc7d44 Compare February 21, 2026 03:32
The __mocks__ directory in src/elements/ was not detected by Vitest for
node_module mocks, so auto-animate ran in tests and leaked timers that
fired after jsdom teardown, causing requestAnimationFrame errors.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/clerk-js/vitest.setup.mts`:
- Around line 265-270: Add unit tests that exercise the new preload/mount flow
and assert the component lifecycle now uses the explicit auto-animate mock:
write tests that simulate the preload step and then mount/render the component
and verify expected post-preload behavior (e.g., DOM state, callbacks invoked,
side effects) and that '@formkit/auto-animate/react' is mocked via
useAutoAnimate returning [null]; update or add tests in the clerk-js test suite
to cover both the preload->mount transition and that useAutoAnimate is not
causing timers or animations to run during teardown to prevent regressions.

Comment on lines +265 to +270
// Mock @formkit/auto-animate to prevent timers leaking after test teardown.
// The __mocks__ directory in src/elements/ is not detected by Vitest for
// node_module mocks, so we need an explicit vi.mock here.
vi.mock('@formkit/auto-animate/react', () => ({
useAutoAnimate: () => [null],
}));
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Feb 21, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add tests covering the behavioral change introduced in this PR.

No tests were added or updated to cover the new preload/mount behavior and the explicit auto-animate mock. Please add/adjust tests to validate the changed flow and guard against regressions.

As per coding guidelines “If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/clerk-js/vitest.setup.mts` around lines 265 - 270, Add unit tests
that exercise the new preload/mount flow and assert the component lifecycle now
uses the explicit auto-animate mock: write tests that simulate the preload step
and then mount/render the component and verify expected post-preload behavior
(e.g., DOM state, callbacks invoked, side effects) and that
'@formkit/auto-animate/react' is mocked via useAutoAnimate returning [null];
update or add tests in the clerk-js test suite to cover both the preload->mount
transition and that useAutoAnimate is not causing timers or animations to run
during teardown to prevent regressions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is being addressed in #7903

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.

@jacekradko, thanks for clarifying! I see that tests are being handled in #7903. That works perfectly.

Copy link
Copy Markdown
Member

@nikosdouvlis nikosdouvlis left a comment

Choose a reason for hiding this comment

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

Makes sense, one question about the open* methods

Comment thread packages/clerk-js/src/core/clerk.ts
Comment thread packages/ui/src/Components.tsx Outdated
@jacekradko jacekradko merged commit 439365e into main Feb 24, 2026
41 checks passed
@jacekradko jacekradko deleted the jacek/user-4747-ui-delay-opening-user-menu-and-org-switcher-on-slow-internet branch February 24, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants