Skip to content

Conversation

@jacekradko
Copy link
Member

@jacekradko jacekradko commented Jan 21, 2026

Summary

Fixes: USER-4231

This PR removes the clerkJSVariant: 'headless' option and replaces it with a new prefetchUI prop that controls whether the @clerk/ui script is prefetched.

Changes

  • Removed: clerkJSVariant: 'headless' option from all SDK packages
  • Added: prefetchUI prop (boolean | undefined) to control UI bundle prefetching
    • prefetchUI={false} - Disable prefetching the UI bundle (UI will still load on-demand when needed)
    • prefetchUI omitted or undefined - Prefetch UI normally (default behavior)
  • Added: shouldPrefetchClerkUI helper function
  • Added: Codemod to automatically migrate clerkJSVariant="headless" to prefetchUI={false}
  • Renamed: clerkUiCtorclerkUICtor for casing consistency
  • Deprecated: ClerkJs casing in favor of ClerkJS for consistency
    • loadClerkJsScriptloadClerkJSScript
    • clerkJsScriptUrlclerkJSScriptUrl
    • buildClerkJsScriptAttributesbuildClerkJSScriptAttributes
    • setClerkJsLoadingErrorPackageNamesetClerkJSLoadingErrorPackageName
    • LoadClerkJsScriptOptionsLoadClerkJSScriptOptions
    • Old names are deprecated aliases that will be removed in a future major version

Environment Variables

You can disable UI prefetching via environment variable:

  • Next.js: NEXT_PUBLIC_CLERK_PREFETCH_UI=false
  • Astro: PUBLIC_CLERK_PREFETCH_UI=false
  • React Router / TanStack Start: CLERK_PREFETCH_UI=false

Usage

// Disable UI prefetching (e.g., when using Control Components for custom UI)
<ClerkProvider prefetchUI={false}>
  {children}
</ClerkProvider>

Packages Updated

  • @clerk/clerk-js (major)
  • @clerk/shared (major)
  • @clerk/react
  • @clerk/nextjs
  • @clerk/astro
  • @clerk/nuxt
  • @clerk/vue
  • @clerk/react-router
  • @clerk/tanstack-react-start
  • @clerk/expo
  • @clerk/chrome-extension
  • @clerk/express
  • @clerk/upgrade (codemod added)

Checklist

  • pnpm build passes
  • Updated integration tests
  • Updated integration templates
  • Added codemod for migration
  • Added upgrade documentation

Summary by CodeRabbit

  • Breaking Changes

    • Removed the headless JS variant and bundle; renamed public options clerkUiUrl → clerkUIUrl and clerkUiCtor → clerkUICtor.
  • New Features

    • Added prefetchUI prop to control UI prefetching (prefetchUI={false} disables).
    • Added environment flags to disable UI prefetching.
  • Chores

    • Version bumps across multiple packages.
  • Tests / Docs

    • Updated tests, codemods, and upgrade docs to guide migration.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Jan 21, 2026

🦋 Changeset detected

Latest commit: 95281f7

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

This PR includes changesets to release 20 packages
Name Type
@clerk/clerk-js Major
@clerk/shared Major
@clerk/react Minor
@clerk/nextjs Minor
@clerk/astro Minor
@clerk/nuxt Minor
@clerk/vue Minor
@clerk/react-router Minor
@clerk/tanstack-react-start Minor
@clerk/expo Minor
@clerk/chrome-extension Minor
@clerk/agent-toolkit Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/testing Patch
@clerk/ui 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

@vercel
Copy link

vercel bot commented Jan 21, 2026

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

Project Deployment Review Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jan 29, 2026 3:39pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

The PR removes the clerkJSVariant option and related headless bundle entrypoints/exports, introduces a boolean prefetchUI flag (and CLERK_PREFETCH_UI handling) to control UI prefetching, renames clerkUiUrlclerkUIUrl, refactors script-loading APIs (e.g., loadClerkJsScriptloadClerkJSScript, clerkUiScriptUrlclerkUIScriptUrl) and adds shouldPrefetchClerkUI. It adds a native bundle variant (clerk.native), updates package exports, types, integrations, env helpers, tests, codemods, and documentation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(clerk-js): remove headless variant' accurately summarizes the main change in the PR, which is the removal of the headless bundle variant from clerk-js.
Linked Issues check ✅ Passed The PR fully implements the requirements from USER-4231: removes the headless bundle, provides a runtime mechanism (prefetchUI prop) to avoid prefetching UI assets, and supplies migration tools (codemod and documentation).
Out of Scope Changes check ✅ Passed All code changes are directly related to removing the headless variant and introducing the prefetchUI feature. Casing consistency updates (clerkJs→clerkJS, clerkUiCtor→clerkUICtor) are architectural improvements supporting the main objective. No unrelated or scope-creeping changes detected.

✏️ 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.

Copy link
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: 2

🤖 Fix all issues with AI agents
In @.changeset/light-eagles-stay.md:
- Around line 1-2: The changeset is empty and must document the breaking changes
introduced: explicitly list affected package names and semver bumps (use major
bumps for breaking changes), describe the removals of clerkJSVariant and the old
import path `@clerk/clerk-js/headless`, and note the new ui prop and its migration
steps; update the changeset body to include a short summary, per-package entries
(e.g., package-name: major), and a brief migration note explaining how to
replace/remove clerkJSVariant, how to update import paths to the new
entrypoints, and how to adopt the new ui prop so consumers can upgrade safely.

In `@packages/expo/src/provider/singleton/createClerkInstance.ts`:
- Around line 1-2: The import path for FapiRequestInit/FapiResponse is invalid;
instead derive request/response types from the public Clerk API: create a
Handler type from Parameters<Clerk["__internal_onBeforeRequest"]>[0], then
extract Req = Parameters<Handler>[0] and use Req as the type for the requestInit
parameter, and similarly extract the response type from
Clerk["__internal_onAfterResponse"] to replace FapiResponse usage; update
function signatures in createClerkInstance.ts to use these derived types
(referencing Clerk, __internal_onBeforeRequest, __internal_onAfterResponse,
Handler, and Req).
🧹 Nitpick comments (1)
packages/react-router/src/client/ReactRouterClerkProvider.tsx (1)

102-109: Type safety concern with as any casts.

The as any casts on lines 102 and 108 bypass TypeScript's type checking. While this may be a workaround for complex generic constraints with TUi, it could mask type mismatches at runtime.

Consider whether these casts can be replaced with more precise type assertions or if the underlying types can be aligned to avoid the need for any.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 21, 2026

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 95281f7

@jacekradko jacekradko changed the title feat(clerk-js): remove headless variant, add ui prop feat(clerk-js): remove headless variant, add prefetchUI prop Jan 21, 2026
Copy link
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: 2

🤖 Fix all issues with AI agents
In `@integration/templates/next-app-router/src/app/layout.tsx`:
- Line 15: The prefetch toggle currently checks the wrong env var
(`NEXT_PUBLIC_CLERK_PREFETCH_UI_DISABLED`) so documented users won't disable
prefetching; update the prop check in app/layout.tsx for the prefetchUI prop to
read `process.env.NEXT_PUBLIC_CLERK_PREFETCH_UI === 'false' ? false : undefined`
(keeping undefined as the default), referencing the existing prefetchUI prop to
locate and replace the condition.

In `@packages/astro/src/server/build-clerk-hotload-script.ts`:
- Around line 37-42: The Astro hotload script builder is not passing the
extracted PUBLIC_CLERK_UI_VERSION through, so pinned UI versions are ignored;
update getSafeEnv to include clerkUIVersion in its returned object (extracting
PUBLIC_CLERK_UI_VERSION into clerkUIVersion) and then forward that value into
the clerkUIScriptUrl call (the clerkUiScriptSrc creation) by adding
clerkUIVersion to the argument list so clerkUIScriptUrl can select the correct
UI package version.

Copy link
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/clerk-js/rspack.config.js (1)

283-297: Add LimitChunkCountPlugin to force the native build into a single chunk.

Dynamic imports exist in production code (e.g., moduleManager.ts, clerk.ts) which would create separate chunks despite splitChunks: false. React Native cannot load those additional chunks at runtime. Match the pattern used for other single-bundle variants:

Proposed fix
     {
       output: {
         publicPath: '',
       },
+      plugins: [
+        new rspack.optimize.LimitChunkCountPlugin({
+          maxChunks: 1,
+        }),
+      ],
       optimization: {
         splitChunks: false,
       },
     },
🤖 Fix all issues with AI agents
In `@packages/astro/src/env.d.ts`:
- Around line 8-9: Add the missing PUBLIC_CLERK_UI_VERSION declaration to the
InternalEnv type: update the InternalEnv interface/type (where
PUBLIC_CLERK_UI_URL and PUBLIC_CLERK_PREFETCH_UI are declared) to include a
readonly PUBLIC_CLERK_UI_VERSION?: string; so TypeScript knows this env var
exists and the use in get-safe-env.ts compiles cleanly.

In `@packages/astro/src/server/get-safe-env.ts`:
- Around line 35-37: Add the missing PUBLIC_CLERK_UI_VERSION declaration to the
InternalEnv type in env.d.ts so the usage in get-safe-env.ts (specifically the
clerkUIVersion key and getContextEnvVar('PUBLIC_CLERK_UI_VERSION', context)) has
a matching type; update the InternalEnv interface/type to include
PUBLIC_CLERK_UI_VERSION with the appropriate string (or optional string) type to
resolve the TS2345 build error.
🧹 Nitpick comments (1)
packages/shared/src/loadClerkJsScript.ts (1)

277-280: TODO comment should be addressed.

Line 278 contains a TODO comment questioning whether this delegation is needed. This should be resolved before merging or tracked in an issue.

Do you want me to open an issue to track this TODO, or should it be addressed in this PR?

Copy link
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

🤖 Fix all issues with AI agents
In `@packages/react/src/isomorphicClerk.ts`:
- Around line 511-518: The function getClerkUiEntryChunk currently returns
undefined early when shouldPrefetchClerkUI(this.options.prefetchUI) is false, so
any provided this.options.clerkUICtor is ignored; reorder the checks in
getClerkUiEntryChunk so you first return this.options.clerkUICtor if present,
and only consult shouldPrefetchClerkUI afterwards. Apply the same reorder in
create-clerk-instance (the equivalent initialization function there) so a
supplied clerkUICtor is honored even when prefetchUI is false.
♻️ Duplicate comments (2)
packages/astro/src/server/get-safe-env.ts (1)

35-37: Ensure PUBLIC_CLERK_UI_VERSION is declared in InternalEnv.

This concern was raised in a previous review. Line 36 references 'PUBLIC_CLERK_UI_VERSION' which must be added to the InternalEnv type in packages/astro/src/env.d.ts to avoid TypeScript errors (TS2345).

packages/astro/src/server/build-clerk-hotload-script.ts (1)

37-43: Past review concern addressed.

The clerkUIVersion is now properly forwarded to clerkUIScriptUrl on line 39, resolving the previously flagged issue about pinned UI versions being ignored.

🧹 Nitpick comments (1)
packages/astro/src/server/build-clerk-hotload-script.ts (1)

9-13: Non-null assertions on potentially undefined values.

The non-null assertions (!) on env.pk, env.proxyUrl, and env.domain could cause runtime issues if these environment variables are not configured. While publishableKey is typically required, proxyUrl and domain are optional and commonly undefined.

Consider removing the non-null assertions for optional values:

Suggested fix
-  // eslint-disable-next-line `@typescript-eslint/no-non-null-assertion`
-  const publishableKey = env.pk!;
-  // eslint-disable-next-line `@typescript-eslint/no-non-null-assertion`
-  const proxyUrl = env.proxyUrl!;
-  // eslint-disable-next-line `@typescript-eslint/no-non-null-assertion`
-  const domain = env.domain!;
+  const publishableKey = env.pk ?? '';
+  const proxyUrl = env.proxyUrl;
+  const domain = env.domain;

- Fix CLERK_PREFETCH_UI_DISABLED env var to use CLERK_PREFETCH_UI=false
- Add clerkUIVersion to IsomorphicClerkOptions for UI version pinning
- Rename __internal_ClerkUiCtor to __internal_ClerkUICtor for consistency
Copy link
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/vue/src/plugin.ts (1)

83-107: prefetchUI=false currently ignores an explicit clerkUICtor (custom UI never loads).

When prefetchUI is false, the code forces clerkUICtorPromise to resolve undefined even if pluginOptions.clerkUICtor is provided (Line 86-97). That drops a valid UI entrypoint and breaks custom UI setups. The same pattern appears in packages/astro/src/internal/create-clerk-instance.ts (Line 120-126). Please prioritize the explicit clerkUICtor and only skip network prefetch when it’s absent.

🔧 Minimal fix (Vue plugin)
-          const clerkUICtorPromise = !shouldPrefetchClerkUI(pluginOptions.prefetchUI)
-            ? Promise.resolve(undefined)
-            : pluginOptions.clerkUICtor
-              ? Promise.resolve(pluginOptions.clerkUICtor)
-              : (async () => {
-                  await loadClerkUIScript(options);
-                  if (!window.__internal_ClerkUICtor) {
-                    throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.');
-                  }
-                  return window.__internal_ClerkUICtor;
-                })();
+          const clerkUICtorPromise = pluginOptions.clerkUICtor
+            ? Promise.resolve(pluginOptions.clerkUICtor)
+            : !shouldPrefetchClerkUI(pluginOptions.prefetchUI)
+              ? Promise.resolve(undefined)
+              : (async () => {
+                  await loadClerkUIScript(options);
+                  if (!window.__internal_ClerkUICtor) {
+                    throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.');
+                  }
+                  return window.__internal_ClerkUICtor;
+                })();

Fixes race condition where shared UI bundle executes before
__clerkSharedModules is registered by @clerk/ui/register.

Using <link rel='preload'> pre-fetches the bundle for performance
but doesn't execute it immediately. The actual execution happens
via loadClerkUIScript() after React code has run.
Reorder checks so explicit clerkUICtor is used even when prefetchUI is
disabled. Previously, setting prefetchUI=false would ignore any custom
UI constructor passed via clerkUICtor option.
@jacekradko
Copy link
Member Author

!snapshot

@clerk-cookie
Copy link
Collaborator

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

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

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

npm i @clerk/agent-toolkit@0.2.9-snapshot.v20260129153510 --save-exact

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/msw

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

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.

4 participants