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 deprecated clerkJSVariant and the headless bundle.
    • Renamed prop clerkUiUrl → clerkUIUrl.
  • New Features

    • Added prefetchUI prop to control UI bundle prefetching (use prefetchUI={false} to disable).
  • Chores

    • Version bumps across multiple Clerk packages (major and minor updates).

✏️ 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: 3fb75ce

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 Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jan 29, 2026 9:14pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

This PR removes the headless bundle and its entry points/tests, replaces the clerkJSVariant option with a boolean prefetchUI control, and adds a clerk.native variant for React Native. It standardizes identifier casing (e.g. clerkJsScriptUrlclerkJSScriptUrl, clerkUiUrlclerkUIUrl, clerkUiCtorclerkUICtor, setClerkJsLoadingErrorPackageNamesetClerkJSLoadingErrorPackageName), updates environment variables and public types, adjusts script loading to optionally preload the UI bundle, and updates integrations, build configs, and codemods accordingly.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 describes the primary change of this PR, which is the removal of the headless bundle and clerkJSVariant option.
Linked Issues check ✅ Passed The PR fully addresses USER-4231 by removing the headless bundle generation and providing a runtime prefetchUI mechanism instead, along with migration tooling.
Out of Scope Changes check ✅ Passed All changes are scope-aligned: removal of headless variant, addition of prefetchUI prop, renaming for casing consistency (clerkUiCtor→clerkUICtor), deprecated aliases, and supporting updates across all related packages.

✏️ 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: 3fb75ce

@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
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

__internal_onAfterSetActive: () => Promise<void> | void;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
__internal_ClerkUiCtor?: import('@clerk/shared/types').ClerkUiConstructor;
__internal_ClerkUICtor?: import('@clerk/shared/types').ClerkUiConstructor;
Copy link
Member Author

Choose a reason for hiding this comment

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

We need to update the corresponding types

avoiding race conditions with __clerkSharedModules registration
(which happens when React code runs @clerk/ui/register).
The actual execution happens via loadClerkUIScript() in isomorphicClerk. */}
{shouldPrefetchClerkUI(prefetchUI) && (
Copy link
Member Author

Choose a reason for hiding this comment

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

I am thinking we should name this prop preloadUI to match the link semantics.. thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Even though it currently works like that, I'm not sure if I like exposing this detail into the surface. But yeah, the terms preload and prefetch are loaded (pun intended) now that we use a link tag

Copy link
Member

@bratsos bratsos left a comment

Choose a reason for hiding this comment

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

I don't really have any feedback, looks really solid! I added 2 minor comments as discussion points. LFG! 🚀

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)

81-102: Omit clerkUICtor from loadOptions when it is not provided and prefetchUI is false.

When prefetchUI === false and no explicit ctor is provided, passing Promise.resolve(undefined) as clerkUICtor causes a runtime error. Although a Promise is truthy and passes the if (this.#options.clerkUICtor) check in Clerk.js, attempting to instantiate undefined as a constructor fails. Additionally, Promise<undefined> violates the ClerkOptions.clerkUICtor type (which expects ClerkUiConstructor | Promise<ClerkUiConstructor>), forcing an unsafe cast.

Suggested fix
-          const clerkUICtorPromise = pluginOptions.clerkUICtor
-            ? Promise.resolve(pluginOptions.clerkUICtor)
-            : pluginOptions.prefetchUI === false
-              ? 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;
-                })();
+          const clerkUICtorPromise =
+            pluginOptions.clerkUICtor
+              ? Promise.resolve(pluginOptions.clerkUICtor)
+              : pluginOptions.prefetchUI === false
+                ? 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;
+                  })();
...
-          const loadOptions = { ...options, clerkUICtor: clerkUICtorPromise } as unknown as ClerkOptions;
+          const loadOptions = {
+            ...options,
+            ...(clerkUICtorPromise ? { clerkUICtor: clerkUICtorPromise } : {}),
+          } as ClerkOptions;

@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.v20260129212211
@clerk/astro 3.0.0-snapshot.v20260129212211
@clerk/backend 3.0.0-snapshot.v20260129212211
@clerk/chrome-extension 3.0.0-snapshot.v20260129212211
@clerk/clerk-js 6.0.0-snapshot.v20260129212211
@clerk/dev-cli 1.0.0-snapshot.v20260129212211
@clerk/expo 3.0.0-snapshot.v20260129212211
@clerk/expo-passkeys 1.0.0-snapshot.v20260129212211
@clerk/express 2.0.0-snapshot.v20260129212211
@clerk/fastify 2.7.0-snapshot.v20260129212211
@clerk/localizations 4.0.0-snapshot.v20260129212211
@clerk/msw 0.0.1-snapshot.v20260129212211
@clerk/nextjs 7.0.0-snapshot.v20260129212211
@clerk/nuxt 2.0.0-snapshot.v20260129212211
@clerk/react 6.0.0-snapshot.v20260129212211
@clerk/react-router 3.0.0-snapshot.v20260129212211
@clerk/shared 4.0.0-snapshot.v20260129212211
@clerk/tanstack-react-start 1.0.0-snapshot.v20260129212211
@clerk/testing 2.0.0-snapshot.v20260129212211
@clerk/ui 1.0.0-snapshot.v20260129212211
@clerk/upgrade 2.0.0-snapshot.v20260129212211
@clerk/vue 2.0.0-snapshot.v20260129212211

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.v20260129212211 --save-exact

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/msw

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

@jacekradko jacekradko merged commit e9a1d4d into main Jan 30, 2026
38 of 40 checks passed
@jacekradko jacekradko deleted the jrad/remove-headless-variant branch January 30, 2026 00:56
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.

5 participants