From 2e52e40999623762f9f13fa8db386fcba43f5b02 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Fri, 9 Jan 2026 13:15:55 +0200 Subject: [PATCH 01/30] feat(nextjs): skip clerk-ui script injection for headless variant why: when using clerkJSVariant='headless', applications only need control components and don't require the full UI bundle. loading the unnecessary @clerk/ui script adds overhead without providing value. what changed: - clerk-script.tsx: conditionally render clerk-ui script tag only when clerkJSVariant !== 'headless' - integration template: read NEXT_PUBLIC_CLERK_JS_VARIANT env var and pass to ClerkProvider users can now set NEXT_PUBLIC_CLERK_JS_VARIANT='headless' to skip loading the ~100KB @clerk/ui bundle when using only control components. --- .../templates/next-app-router/src/app/layout.tsx | 1 + packages/nextjs/src/utils/clerk-script.tsx | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx index 42341d04adc..2db280398a6 100644 --- a/integration/templates/next-app-router/src/app/layout.tsx +++ b/integration/templates/next-app-router/src/app/layout.tsx @@ -12,6 +12,7 @@ export const metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + {clerkJSVariant !== 'headless' && ( + + )} ); } From 6ab9de38cc49b4795108a714f39b6b84d43d674c Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Fri, 9 Jan 2026 13:16:07 +0200 Subject: [PATCH 02/30] feat(astro): skip clerk-ui script injection for headless variant why: when using clerkJSVariant='headless', applications only need control components and don't require the full UI bundle. loading the unnecessary @clerk/ui script adds overhead without providing value. what changed: - build-clerk-hotload-script: skip generating clerk-ui script tag when clerkJsVariant === 'headless' - create-clerk-instance: getClerkUiEntryChunk returns undefined for headless variant to skip client-side hot-loading users can now set clerkJSVariant='headless' to skip loading the ~100KB @clerk/ui bundle when using only control components. --- .../src/internal/create-clerk-instance.ts | 8 ++++- .../src/server/build-clerk-hotload-script.ts | 32 +++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index e1cbd520144..655d5c2695e 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -111,10 +111,16 @@ async function getClerkJsEntryChunk(options?: AstroClerkCre /** * Gets the ClerkUI constructor, either from options or by loading the script. * Returns early if window.__internal_ClerkUiCtor already exists. + * Returns undefined for headless variant (no UI needed). */ async function getClerkUiEntryChunk( options?: AstroClerkCreateInstanceParams, -): Promise { +): Promise { + // Skip UI loading for headless variant + if (options?.clerkJSVariant === 'headless') { + return undefined; + } + if (options?.clerkUiCtor) { return options.clerkUiCtor; } diff --git a/packages/astro/src/server/build-clerk-hotload-script.ts b/packages/astro/src/server/build-clerk-hotload-script.ts index b3cf7d37089..44a5a5c6b2c 100644 --- a/packages/astro/src/server/build-clerk-hotload-script.ts +++ b/packages/astro/src/server/build-clerk-hotload-script.ts @@ -10,21 +10,17 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { const proxyUrl = getSafeEnv(locals).proxyUrl!; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const domain = getSafeEnv(locals).domain!; + const clerkJsVariant = getSafeEnv(locals).clerkJsVariant; const clerkJsScriptSrc = clerkJsScriptUrl({ clerkJSUrl: getSafeEnv(locals).clerkJsUrl, - clerkJSVariant: getSafeEnv(locals).clerkJsVariant, + clerkJSVariant: clerkJsVariant, clerkJSVersion: getSafeEnv(locals).clerkJsVersion, domain, proxyUrl, publishableKey, }); - const clerkUiScriptSrc = clerkUiScriptUrl({ - clerkUiUrl: getSafeEnv(locals).clerkUiUrl, - domain, - proxyUrl, - publishableKey, - }); - return ` + + const clerkJsScript = ` + >`; + + // Skip clerk-ui script for headless variant + if (clerkJsVariant === 'headless') { + return clerkJsScript + '\n'; + } + + const clerkUiScriptSrc = clerkUiScriptUrl({ + clerkUiUrl: getSafeEnv(locals).clerkUiUrl, + domain, + proxyUrl, + publishableKey, + }); + + const clerkUiScript = ` \n`; + >`; + + return clerkJsScript + clerkUiScript + '\n'; } export { buildClerkHotloadScript }; From 9a82f92a765e5a612025f5e3f4bfb4edfe06d0b4 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Fri, 9 Jan 2026 13:16:17 +0200 Subject: [PATCH 03/30] feat(react): skip clerk-ui loading for headless variant why: when using clerkJSVariant='headless', applications only need control components and don't require the full UI bundle. loading the unnecessary @clerk/ui script adds overhead without providing value. what changed: isomorphicClerk's getClerkUiEntryChunk method now returns undefined when clerkJSVariant === 'headless', skipping the loadClerkUiScript call entirely. users can now set clerkJSVariant='headless' to skip loading the ~100KB @clerk/ui bundle when using only control components. --- packages/react/src/isomorphicClerk.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b39bf352b8c..1de5a4ee295 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -508,7 +508,12 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { return global.Clerk; } - private async getClerkUiEntryChunk(): Promise { + private async getClerkUiEntryChunk(): Promise { + // Skip UI loading for headless variant + if (this.options.clerkJSVariant === 'headless') { + return undefined; + } + if (this.options.clerkUiCtor) { return this.options.clerkUiCtor; } From d5f819a29dc2f01d87fa52d5cafb9bc88897d7c3 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Fri, 9 Jan 2026 13:16:28 +0200 Subject: [PATCH 04/30] feat(vue): skip clerk-ui loading for headless variant why: when using clerkJSVariant='headless', applications only need control components and don't require the full UI bundle. loading the unnecessary @clerk/ui script adds overhead without providing value. what changed: clerkPlugin now checks if clerkJSVariant === 'headless' and skips the loadClerkUiScript call, resolving the clerkUiCtorPromise to undefined instead. users can now set clerkJSVariant='headless' to skip loading the ~100KB @clerk/ui bundle when using only control components. --- packages/vue/src/plugin.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index 91a89ffe91d..f6ed9933884 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -78,15 +78,19 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { void (async () => { try { const clerkPromise = loadClerkJsScript(options); - const clerkUiCtorPromise = 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; - })(); + // Skip UI loading for headless variant + const clerkUiCtorPromise = + pluginOptions.clerkJSVariant === 'headless' + ? 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; + })(); await clerkPromise; From a2e16cd1e7d8a08a7549a05eef2ddf366723d0f9 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Fri, 9 Jan 2026 13:16:39 +0200 Subject: [PATCH 05/30] test(e2e): add headless variant test for nextjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit why: verify that the headless variant correctly skips clerk-ui script injection across the full integration stack (env var → prop → script rendering). what changed: created headless-variant.test.ts that sets CLERK_JS_VARIANT='headless' and asserts clerk-ui script is absent while clerk-js script is present. --- integration/tests/headless-variant.test.ts | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 integration/tests/headless-variant.test.ts diff --git a/integration/tests/headless-variant.test.ts b/integration/tests/headless-variant.test.ts new file mode 100644 index 00000000000..e8861242029 --- /dev/null +++ b/integration/tests/headless-variant.test.ts @@ -0,0 +1,32 @@ +import { expect, test } from '@playwright/test'; + +import type { Application } from '../models/application'; +import { appConfigs } from '../presets'; + +test.describe('headless variant @nextjs', () => { + test.describe.configure({ mode: 'serial' }); + let app: Application; + + test.beforeAll(async () => { + app = await appConfigs.next.appRouter.clone().commit(); + await app.setup(); + // Use withEmailCodes but add the headless variant + const env = appConfigs.envs.withEmailCodes.clone().setEnvVariable('public', 'CLERK_JS_VARIANT', 'headless'); + await app.withEnv(env); + await app.dev(); + }); + + test.afterAll(async () => { + await app.teardown(); + }); + + test('does not inject clerk-ui script when headless variant is used', async ({ page }) => { + await page.goto(app.serverUrl); + + // Wait for clerk-js script to be present (ensures page has loaded) + await expect(page.locator('script[data-clerk-js-script]')).toBeAttached(); + + // clerk-ui script should NOT be present + await expect(page.locator('script[data-clerk-ui-script]')).not.toBeAttached(); + }); +}); From f643280bd78ff2ad27049279d94140316f79cfa7 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Mon, 12 Jan 2026 12:47:14 +0200 Subject: [PATCH 06/30] dedupe --- .changeset/light-eagles-stay.md | 2 ++ packages/react/src/isomorphicClerk.ts | 2 +- pnpm-lock.yaml | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/light-eagles-stay.md diff --git a/.changeset/light-eagles-stay.md b/.changeset/light-eagles-stay.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/light-eagles-stay.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 1de5a4ee295..b9e0064dc1c 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -461,7 +461,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } try { - const clerkUiCtor = this.getClerkUiEntryChunk(); + const clerkUiCtor = await this.getClerkUiEntryChunk(); const clerk = await this.getClerkJsEntryChunk(); if (!clerk.loaded) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57d54b6b39e..3424adde0e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2460,7 +2460,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -14908,10 +14908,12 @@ packages: whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} From 320bddeda18acdf73d4565330bc903713a100576 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 20 Jan 2026 22:27:42 -0600 Subject: [PATCH 07/30] feat(clerk-js): remove headless variant, use react-native export condition The headless variant is no longer needed now that UI components have been moved to @clerk/ui. The browser builds are now identical in size. Changes: - Add `react-native` export condition in package.json for Expo/RN - Rename `clerkHeadless` build to `clerkNative` (no chunk splitting) - Remove `clerkHeadlessBrowser` build (identical to regular browser) - Update Expo to import from `@clerk/clerk-js` instead of `/headless` - Deprecate `clerkJSVariant` option (now ignored) - Delete headless source files and export directory BREAKING CHANGE: `@clerk/clerk-js/headless` import path removed. Expo/React Native users should import from `@clerk/clerk-js` directly. --- packages/clerk-js/bundlewatch.config.json | 2 +- packages/clerk-js/headless/index.d.ts | 3 -- packages/clerk-js/headless/index.js | 1 - packages/clerk-js/package.json | 18 ++++++- packages/clerk-js/rspack.config.js | 52 ++++--------------- .../clerk-js/src/index.headless.browser.ts | 30 ----------- packages/clerk-js/src/index.headless.ts | 22 -------- .../provider/singleton/createClerkInstance.ts | 2 +- .../expo/src/provider/singleton/singleton.ts | 2 +- .../src/__tests__/loadClerkJsScript.spec.ts | 7 ++- packages/shared/src/loadClerkJsScript.ts | 8 +-- packages/shared/src/types/clerk.ts | 3 +- 12 files changed, 40 insertions(+), 110 deletions(-) delete mode 100644 packages/clerk-js/headless/index.d.ts delete mode 100644 packages/clerk-js/headless/index.js delete mode 100644 packages/clerk-js/src/index.headless.browser.ts delete mode 100644 packages/clerk-js/src/index.headless.ts diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 834dd41db10..4f78f5c0554 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -5,7 +5,7 @@ { "path": "./dist/clerk.chips.browser.js", "maxSize": "66KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "105KB" }, { "path": "./dist/clerk.no-rhc.js", "maxSize": "305KB" }, - { "path": "./dist/clerk.headless*.js", "maxSize": "65KB" }, + { "path": "./dist/clerk.native.js", "maxSize": "65KB" }, { "path": "./dist/vendors*.js", "maxSize": "7KB" }, { "path": "./dist/coinbase*.js", "maxSize": "36KB" }, { "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" }, diff --git a/packages/clerk-js/headless/index.d.ts b/packages/clerk-js/headless/index.d.ts deleted file mode 100644 index b29913ac3f0..00000000000 --- a/packages/clerk-js/headless/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { Clerk } from '../dist/types/index.headless'; - -export * from '../dist/types/index.headless'; diff --git a/packages/clerk-js/headless/index.js b/packages/clerk-js/headless/index.js deleted file mode 100644 index eb34c85affa..00000000000 --- a/packages/clerk-js/headless/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../dist/clerk.headless'); diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index b747bd2ec60..441096ac92c 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -25,9 +25,24 @@ "jsdelivr": "dist/clerk.browser.js", "module": "dist/clerk.mjs", "types": "dist/types/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.native.js" + }, + "import": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.mjs" + }, + "require": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.js" + } + } + }, "files": [ "dist", - "headless", "no-rhc" ], "scripts": { @@ -42,7 +57,6 @@ "clean": "rimraf ./dist", "dev": "rspack serve --config rspack.config.js", "dev:chips": "rspack serve --config rspack.config.js --env variant=\"clerk.chips.browser\"", - "dev:headless": "rspack serve --config rspack.config.js --env variant=\"clerk.headless.browser\"", "dev:origin": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:${PORT:-4000}", "dev:sandbox": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:${PORT:-4000} --env sandbox=1", "format": "node ../../scripts/format-package.mjs", diff --git a/packages/clerk-js/rspack.config.js b/packages/clerk-js/rspack.config.js index 55133e59808..843ceabc177 100644 --- a/packages/clerk-js/rspack.config.js +++ b/packages/clerk-js/rspack.config.js @@ -14,8 +14,7 @@ const variants = { clerk: 'clerk', clerkNoRHC: 'clerk.no-rhc', // Omit Remotely Hosted Code clerkBrowser: 'clerk.browser', - clerkHeadless: 'clerk.headless', - clerkHeadlessBrowser: 'clerk.headless.browser', + clerkNative: 'clerk.native', // For React Native (no chunk splitting) clerkLegacyBrowser: 'clerk.legacy.browser', clerkCHIPS: 'clerk.chips.browser', }; @@ -24,8 +23,7 @@ const variantToSourceFile = { [variants.clerk]: './src/index.ts', [variants.clerkNoRHC]: './src/index.ts', [variants.clerkBrowser]: './src/index.browser.ts', - [variants.clerkHeadless]: './src/index.headless.ts', - [variants.clerkHeadlessBrowser]: './src/index.headless.browser.ts', + [variants.clerkNative]: './src/index.ts', [variants.clerkLegacyBrowser]: './src/index.legacy.browser.ts', [variants.clerkCHIPS]: './src/index.browser.ts', }; @@ -229,16 +227,6 @@ const commonForProd = () => { }; }; -// /** @type { () => (import('webpack').Configuration) } */ -// const externalsForHeadless = () => { -// return { -// externals: { -// react: 'react', -// 'react-dom': 'react-dom', -// }, -// }; -// }; - /** * * @param {string} variant @@ -292,14 +280,13 @@ const prodConfig = ({ mode, env, analysis }) => { commonForProdChunked({ targets: packageJSON.browserslistLegacy, useCoreJs: true }), ); - const clerkHeadless = merge( - entryForVariant(variants.clerkHeadless), - common({ mode, variant: variants.clerkHeadless }), + const clerkNative = merge( + entryForVariant(variants.clerkNative), + common({ mode, variant: variants.clerkNative }), commonForProd(), commonForProdChunked(), - // Disable chunking for the headless variant, since it's meant to be used in a non-browser environment and - // attempting to load chunks causes issues due to usage of a dynamic publicPath. We generally are only concerned with - // chunking in our browser bundles. + // Disable chunking for the native variant, since it's meant to be used in React Native + // where dynamic chunk loading is not supported. { output: { publicPath: '', @@ -308,15 +295,6 @@ const prodConfig = ({ mode, env, analysis }) => { splitChunks: false, }, }, - // externalsForHeadless(), - ); - - const clerkHeadlessBrowser = merge( - entryForVariant(variants.clerkHeadlessBrowser), - common({ mode, variant: variants.clerkHeadlessBrowser }), - commonForProd(), - commonForProdChunked(), - // externalsForHeadless(), ); const clerkCHIPS = merge( @@ -437,8 +415,7 @@ const prodConfig = ({ mode, env, analysis }) => { return [ clerkBrowser, clerkLegacyBrowser, - clerkHeadless, - clerkHeadlessBrowser, + clerkNative, clerkCHIPS, clerkEsm, clerkEsmNoRHC, @@ -534,17 +511,10 @@ const devConfig = ({ mode, env }) => { common({ mode, disableRHC: true, variant: variants.clerkBrowserNoRHC }), commonForDev(), ), - [variants.clerkHeadless]: merge( - entryForVariant(variants.clerkHeadless), - common({ mode, variant: variants.clerkHeadless }), - commonForDev(), - // externalsForHeadless(), - ), - [variants.clerkHeadlessBrowser]: merge( - entryForVariant(variants.clerkHeadlessBrowser), - common({ mode, variant: variants.clerkHeadlessBrowser }), + [variants.clerkNative]: merge( + entryForVariant(variants.clerkNative), + common({ mode, variant: variants.clerkNative }), commonForDev(), - // externalsForHeadless(), ), [variants.clerkCHIPS]: merge( entryForVariant(variants.clerkCHIPS), diff --git a/packages/clerk-js/src/index.headless.browser.ts b/packages/clerk-js/src/index.headless.browser.ts deleted file mode 100644 index 45fcfdbc7d8..00000000000 --- a/packages/clerk-js/src/index.headless.browser.ts +++ /dev/null @@ -1,30 +0,0 @@ -// It's crucial this is the first import, -// otherwise chunk loading will not work - -import './utils/setWebpackChunkPublicPath'; - -import { Clerk } from './core/clerk'; - -const publishableKey = - document.querySelector('script[data-clerk-publishable-key]')?.getAttribute('data-clerk-publishable-key') || - window.__clerk_publishable_key || - ''; - -const proxyUrl = - document.querySelector('script[data-clerk-proxy-url]')?.getAttribute('data-clerk-proxy-url') || - window.__clerk_proxy_url || - ''; - -const domain = - document.querySelector('script[data-clerk-domain]')?.getAttribute('data-clerk-domain') || window.__clerk_domain || ''; - -if (!window.Clerk) { - window.Clerk = new Clerk(publishableKey, { - proxyUrl, - domain, - }); -} - -if (module.hot) { - module.hot.accept(); -} diff --git a/packages/clerk-js/src/index.headless.ts b/packages/clerk-js/src/index.headless.ts deleted file mode 100644 index 82812769c40..00000000000 --- a/packages/clerk-js/src/index.headless.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Clerk } from './core/clerk'; - -export { - ClerkAPIResponseError, - ClerkRuntimeError, - EmailLinkError, - EmailLinkErrorCode, - EmailLinkErrorCodeStatus, - isClerkAPIResponseError, - isClerkRuntimeError, - isEmailLinkError, - isKnownError, - isMetamaskError, - isUserLockedError, - type MetamaskError, -} from '@clerk/shared/error'; - -export { Clerk }; - -if (module.hot) { - module.hot.accept(); -} diff --git a/packages/expo/src/provider/singleton/createClerkInstance.ts b/packages/expo/src/provider/singleton/createClerkInstance.ts index f80234590d6..00ea7127ad0 100644 --- a/packages/expo/src/provider/singleton/createClerkInstance.ts +++ b/packages/expo/src/provider/singleton/createClerkInstance.ts @@ -1,5 +1,5 @@ import type { FapiRequestInit, FapiResponse } from '@clerk/clerk-js/dist/types/core/fapiClient'; -import { type Clerk, isClerkRuntimeError } from '@clerk/clerk-js/headless'; +import { type Clerk, isClerkRuntimeError } from '@clerk/clerk-js'; import type { BrowserClerk, HeadlessBrowserClerk } from '@clerk/react'; import { is4xxError } from '@clerk/shared/error'; import type { diff --git a/packages/expo/src/provider/singleton/singleton.ts b/packages/expo/src/provider/singleton/singleton.ts index 22e6fc4ca16..eaf34ba0f3f 100644 --- a/packages/expo/src/provider/singleton/singleton.ts +++ b/packages/expo/src/provider/singleton/singleton.ts @@ -1,4 +1,4 @@ -import { Clerk } from '@clerk/clerk-js/headless'; +import { Clerk } from '@clerk/clerk-js'; import { createClerkInstance } from './createClerkInstance'; diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index f25cec679ff..1ca63833649 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -165,11 +165,10 @@ describe('clerkJsScriptUrl()', () => { expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); }); - test('includes clerkJSVariant in URL when provided', () => { + test('ignores deprecated clerkJSVariant option', () => { const result = clerkJsScriptUrl({ publishableKey: mockProdPublishableKey, clerkJSVariant: 'headless' }); - expect(result).toBe( - `https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.headless.browser.js`, - ); + // clerkJSVariant is deprecated and should be ignored - always returns the standard browser bundle + expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); }); test('uses provided clerkJSVersion', () => { diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 2e3a6012dbe..c2f41b00987 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -13,6 +13,9 @@ const errorThrower = buildErrorThrower({ packageName: '@clerk/shared' }); export type LoadClerkJsScriptOptions = { publishableKey: string; clerkJSUrl?: string; + /** + * @deprecated This option is no longer needed and will be ignored. + */ clerkJSVariant?: 'headless' | ''; clerkJSVersion?: string; sdkMetadata?: SDKMetadata; @@ -216,16 +219,15 @@ export const loadClerkUiScript = async (opts?: LoadClerkUiScriptOptions): Promis }; export const clerkJsScriptUrl = (opts: LoadClerkJsScriptOptions) => { - const { clerkJSUrl, clerkJSVariant, clerkJSVersion, proxyUrl, domain, publishableKey } = opts; + const { clerkJSUrl, clerkJSVersion, proxyUrl, domain, publishableKey } = opts; if (clerkJSUrl) { return clerkJSUrl; } const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); - const variant = clerkJSVariant ? `${clerkJSVariant.replace(/\.+$/, '')}.` : ''; const version = versionSelector(clerkJSVersion); - return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.${variant}browser.js`; + return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`; }; export const clerkUiScriptUrl = (opts: LoadClerkUiScriptOptions) => { diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 9d3f7945f38..796d70b51cd 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2398,7 +2398,8 @@ export type IsomorphicClerkOptions = Without & { */ clerkJSUrl?: string; /** - * If your web application only uses [Control Components](https://clerk.com/docs/reference/components/overview#control-components), you can set this value to `'headless'` and load a minimal ClerkJS bundle for optimal page performance. + * @deprecated This option is no longer needed. The headless variant has been removed + * as the standard clerk-js bundle no longer includes UI components. */ clerkJSVariant?: 'headless' | ''; /** From bb62787aacb31d97666b94aa434154dfa65b1fce Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 06:57:16 -0600 Subject: [PATCH 08/30] fix: un-deprecate clerkJSVariant, update docs clerkJSVariant is still used to control whether @clerk/ui loads. The option just no longer affects the clerk-js URL since the separate headless build has been removed. --- packages/shared/src/__tests__/loadClerkJsScript.spec.ts | 5 +++-- packages/shared/src/loadClerkJsScript.ts | 4 +++- packages/shared/src/types/clerk.ts | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index 1ca63833649..27146ec1197 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -165,9 +165,10 @@ describe('clerkJsScriptUrl()', () => { expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); }); - test('ignores deprecated clerkJSVariant option', () => { + test('clerkJSVariant does not affect URL (headless build removed)', () => { const result = clerkJsScriptUrl({ publishableKey: mockProdPublishableKey, clerkJSVariant: 'headless' }); - // clerkJSVariant is deprecated and should be ignored - always returns the standard browser bundle + // The separate headless build has been removed - always returns standard browser bundle + // clerkJSVariant is still used to control UI loading, but not the clerk-js URL expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); }); diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index c2f41b00987..dec0d12aaea 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -14,7 +14,9 @@ export type LoadClerkJsScriptOptions = { publishableKey: string; clerkJSUrl?: string; /** - * @deprecated This option is no longer needed and will be ignored. + * Set to `'headless'` to skip loading the `@clerk/ui` script. + * Note: The separate headless build of clerk-js has been removed as it's + * now identical to the standard build. This option only controls UI loading. */ clerkJSVariant?: 'headless' | ''; clerkJSVersion?: string; diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 796d70b51cd..d2f9e7ef680 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2398,8 +2398,8 @@ export type IsomorphicClerkOptions = Without & { */ clerkJSUrl?: string; /** - * @deprecated This option is no longer needed. The headless variant has been removed - * as the standard clerk-js bundle no longer includes UI components. + * Set to `'headless'` to skip loading the `@clerk/ui` script. Use this when building + * custom UIs with Control Components and you don't need the prebuilt Clerk UI. */ clerkJSVariant?: 'headless' | ''; /** From bc48c7e8abc973633f65eb79e281faafb352d1dd Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 07:46:12 -0600 Subject: [PATCH 09/30] feat: rename clerkJSVariant to ui prop for controlling UI script loading Replace the clerkJSVariant: 'headless' pattern with a cleaner ui prop API: - ui: false - Skip loading @clerk/ui (for custom UIs) - ui: { version?, url? } - Load UI with specific version/URL - ui: undefined (default) - Load UI normally Also adds shouldLoadClerkUi() helper function to shared package. Breaking change: clerkJSVariant is removed in favor of ui prop. --- packages/astro/src/env.d.ts | 5 ++- .../src/integration/create-integration.ts | 21 ++++------ .../src/internal/create-clerk-instance.ts | 12 ++++-- .../internal/merge-env-vars-with-params.ts | 40 +++++++++++++++++-- .../src/server/build-clerk-hotload-script.ts | 22 +++++----- packages/astro/src/server/get-safe-env.ts | 7 +++- packages/astro/src/types.ts | 8 ++-- packages/express/src/utils.ts | 17 +++++++- .../nextjs/src/pages/__tests__/index.test.tsx | 26 +++++++++--- packages/nextjs/src/utils/clerk-script.tsx | 10 ++--- .../src/utils/mergeNextClerkPropsWithEnv.ts | 30 +++++++++++++- packages/nuxt/src/global.d.ts | 7 +--- packages/nuxt/src/module.ts | 8 ++-- packages/nuxt/src/runtime/plugin.ts | 3 +- .../src/client/ReactRouterClerkProvider.tsx | 8 ++-- packages/react-router/src/client/types.ts | 2 +- packages/react-router/src/server/utils.ts | 11 ++--- packages/react-router/src/utils/env.ts | 18 ++++++++- .../contexts/__tests__/ClerkProvider.test.tsx | 26 +++++++++--- packages/react/src/internal.ts | 1 + packages/react/src/isomorphicClerk.ts | 11 ++--- .../src/__tests__/loadClerkJsScript.spec.ts | 34 ++++++++++++---- packages/shared/src/loadClerkJsScript.ts | 15 ++++--- packages/shared/src/types/clerk.ts | 18 +++------ .../tanstack-react-start/src/client/types.ts | 2 +- .../tanstack-react-start/src/client/utils.ts | 22 +++++----- .../src/server/constants.ts | 2 +- .../src/server/utils/index.ts | 15 ++++++- .../tanstack-react-start/src/utils/env.ts | 18 ++++++++- packages/vue/src/plugin.ts | 36 ++++++++++------- 30 files changed, 311 insertions(+), 144 deletions(-) diff --git a/packages/astro/src/env.d.ts b/packages/astro/src/env.d.ts index 8ead567ea80..e7140423b2d 100644 --- a/packages/astro/src/env.d.ts +++ b/packages/astro/src/env.d.ts @@ -4,9 +4,10 @@ interface InternalEnv { readonly PUBLIC_CLERK_FRONTEND_API?: string; readonly PUBLIC_CLERK_PUBLISHABLE_KEY?: string; readonly PUBLIC_CLERK_JS_URL?: string; - readonly PUBLIC_CLERK_UI_URL?: string; - readonly PUBLIC_CLERK_JS_VARIANT?: 'headless' | ''; readonly PUBLIC_CLERK_JS_VERSION?: string; + readonly PUBLIC_CLERK_UI_DISABLED?: string; + readonly PUBLIC_CLERK_UI_URL?: string; + readonly PUBLIC_CLERK_UI_VERSION?: string; readonly CLERK_API_KEY?: string; readonly CLERK_API_URL?: string; readonly CLERK_API_VERSION?: string; diff --git a/packages/astro/src/integration/create-integration.ts b/packages/astro/src/integration/create-integration.ts index e8902271a96..f1d6115aa29 100644 --- a/packages/astro/src/integration/create-integration.ts +++ b/packages/astro/src/integration/create-integration.ts @@ -20,9 +20,8 @@ function createIntegration() // These are not provided when the "bundled" integration is used const clerkJSUrl = (params as any)?.clerkJSUrl as string | undefined; - const clerkUiUrl = (params as any)?.clerkUiUrl as string | undefined; - const clerkJSVariant = (params as any)?.clerkJSVariant as string | undefined; const clerkJSVersion = (params as any)?.clerkJSVersion as string | undefined; + const ui = (params as any)?.ui as false | { version?: string; url?: string } | undefined; return { name: '@clerk/astro/integration', @@ -32,9 +31,6 @@ function createIntegration() logger.error('Missing adapter, please update your Astro config to use one.'); } - if (typeof clerkJSVariant !== 'undefined' && clerkJSVariant !== 'headless' && clerkJSVariant !== '') { - logger.error('Invalid value for clerkJSVariant. Acceptable values are `"headless"`, `""`, and `undefined`'); - } const internalParams: ClerkOptions = { ...params, @@ -61,9 +57,10 @@ function createIntegration() ...buildEnvVarFromOption(proxyUrl, 'PUBLIC_CLERK_PROXY_URL'), ...buildEnvVarFromOption(domain, 'PUBLIC_CLERK_DOMAIN'), ...buildEnvVarFromOption(clerkJSUrl, 'PUBLIC_CLERK_JS_URL'), - ...buildEnvVarFromOption(clerkUiUrl, 'PUBLIC_CLERK_UI_URL'), - ...buildEnvVarFromOption(clerkJSVariant, 'PUBLIC_CLERK_JS_VARIANT'), ...buildEnvVarFromOption(clerkJSVersion, 'PUBLIC_CLERK_JS_VERSION'), + ...buildEnvVarFromOption(ui === false, 'PUBLIC_CLERK_UI_DISABLED'), + ...buildEnvVarFromOption(typeof ui === 'object' ? ui.url : undefined, 'PUBLIC_CLERK_UI_URL'), + ...buildEnvVarFromOption(typeof ui === 'object' ? ui.version : undefined, 'PUBLIC_CLERK_UI_VERSION'), }, ssr: { @@ -170,14 +167,10 @@ function createClerkEnvSchema() { PUBLIC_CLERK_PROXY_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }), PUBLIC_CLERK_DOMAIN: envField.string({ context: 'client', access: 'public', optional: true, url: true }), PUBLIC_CLERK_JS_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }), - PUBLIC_CLERK_UI_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }), - PUBLIC_CLERK_JS_VARIANT: envField.enum({ - context: 'client', - access: 'public', - optional: true, - values: ['headless'], - }), PUBLIC_CLERK_JS_VERSION: envField.string({ context: 'client', access: 'public', optional: true }), + PUBLIC_CLERK_UI_DISABLED: envField.boolean({ context: 'client', access: 'public', optional: true }), + PUBLIC_CLERK_UI_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }), + PUBLIC_CLERK_UI_VERSION: envField.string({ context: 'client', access: 'public', optional: true }), PUBLIC_CLERK_TELEMETRY_DISABLED: envField.boolean({ context: 'client', access: 'public', optional: true }), PUBLIC_CLERK_TELEMETRY_DEBUG: envField.boolean({ context: 'client', access: 'public', optional: true }), CLERK_SECRET_KEY: envField.string({ context: 'server', access: 'secret' }), diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index 655d5c2695e..7005953a472 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -2,6 +2,7 @@ import { loadClerkJsScript, loadClerkUiScript, setClerkJsLoadingErrorPackageName, + shouldLoadClerkUi, } from '@clerk/shared/loadClerkJsScript'; import type { ClerkOptions } from '@clerk/shared/types'; import type { ClerkUiConstructor } from '@clerk/shared/ui'; @@ -111,13 +112,12 @@ async function getClerkJsEntryChunk(options?: AstroClerkCre /** * Gets the ClerkUI constructor, either from options or by loading the script. * Returns early if window.__internal_ClerkUiCtor already exists. - * Returns undefined for headless variant (no UI needed). + * Returns undefined when ui={false} (no UI needed). */ async function getClerkUiEntryChunk( options?: AstroClerkCreateInstanceParams, ): Promise { - // Skip UI loading for headless variant - if (options?.clerkJSVariant === 'headless') { + if (!shouldLoadClerkUi(options?.ui)) { return undefined; } @@ -125,7 +125,11 @@ async function getClerkUiEntryChunk( return options.clerkUiCtor; } - await loadClerkUiScript(options); + await loadClerkUiScript({ + ...(options as any), + clerkUiUrl: typeof options?.ui === 'object' ? options.ui.url : undefined, + clerkUiVersion: typeof options?.ui === 'object' ? options.ui.version : undefined, + }); if (!window.__internal_ClerkUiCtor) { throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index dea24762b16..e7277b8f4e6 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -2,6 +2,40 @@ import { isTruthy } from '@clerk/shared/underscore'; import type { AstroClerkIntegrationParams } from '../types'; +/** + * Merges `ui` param with env vars. + * - If param `ui` is explicitly `false`, return `false` + * - If env `PUBLIC_CLERK_UI_DISABLED` is "true", return `false` + * - Otherwise merge param and env values for url/version + */ +function mergeUiConfig( + paramUi: AstroClerkIntegrationParams['ui'], +): false | { url?: string; version?: string } | undefined { + // Explicit false from param takes precedence + if (paramUi === false) { + return false; + } + + // Check env var for disabled + if (import.meta.env.PUBLIC_CLERK_UI_DISABLED === 'true') { + return false; + } + + const envUrl = import.meta.env.PUBLIC_CLERK_UI_URL; + const envVersion = import.meta.env.PUBLIC_CLERK_UI_VERSION; + + // Merge param values with env fallbacks + const url = paramUi?.url || envUrl; + const version = paramUi?.version || envVersion; + + // Return undefined if no values set, otherwise return config object + if (!url && !version) { + return undefined; + } + + return { url, version }; +} + /** * @internal */ @@ -15,9 +49,8 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish publishableKey: paramPublishableKey, telemetry: paramTelemetry, clerkJSUrl: paramClerkJSUrl, - clerkUiUrl: paramClerkUiUrl, - clerkJSVariant: paramClerkJSVariant, clerkJSVersion: paramClerkJSVersion, + ui: paramUi, ...rest } = params || {}; @@ -28,10 +61,9 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish proxyUrl: paramProxy || import.meta.env.PUBLIC_CLERK_PROXY_URL, domain: paramDomain || import.meta.env.PUBLIC_CLERK_DOMAIN, publishableKey: paramPublishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', - clerkUiUrl: paramClerkUiUrl || import.meta.env.PUBLIC_CLERK_UI_URL, clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, - clerkJSVariant: paramClerkJSVariant || import.meta.env.PUBLIC_CLERK_JS_VARIANT, clerkJSVersion: paramClerkJSVersion || import.meta.env.PUBLIC_CLERK_JS_VERSION, + ui: mergeUiConfig(paramUi), telemetry: paramTelemetry || { disabled: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DISABLED), debug: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DEBUG), diff --git a/packages/astro/src/server/build-clerk-hotload-script.ts b/packages/astro/src/server/build-clerk-hotload-script.ts index 44a5a5c6b2c..81d45aec59d 100644 --- a/packages/astro/src/server/build-clerk-hotload-script.ts +++ b/packages/astro/src/server/build-clerk-hotload-script.ts @@ -1,20 +1,20 @@ -import { clerkJsScriptUrl, clerkUiScriptUrl } from '@clerk/shared/loadClerkJsScript'; +import { clerkJsScriptUrl, clerkUiScriptUrl, shouldLoadClerkUi } from '@clerk/shared/loadClerkJsScript'; import type { APIContext } from 'astro'; import { getSafeEnv } from './get-safe-env'; function buildClerkHotloadScript(locals: APIContext['locals']) { + const env = getSafeEnv(locals); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const publishableKey = getSafeEnv(locals).pk!; + const publishableKey = env.pk!; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const proxyUrl = getSafeEnv(locals).proxyUrl!; + const proxyUrl = env.proxyUrl!; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const domain = getSafeEnv(locals).domain!; - const clerkJsVariant = getSafeEnv(locals).clerkJsVariant; + const domain = env.domain!; + const clerkJsScriptSrc = clerkJsScriptUrl({ - clerkJSUrl: getSafeEnv(locals).clerkJsUrl, - clerkJSVariant: clerkJsVariant, - clerkJSVersion: getSafeEnv(locals).clerkJsVersion, + clerkJSUrl: env.clerkJsUrl, + clerkJSVersion: env.clerkJsVersion, domain, proxyUrl, publishableKey, @@ -30,13 +30,13 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { ${domain ? `data-clerk-domain="${domain}"` : ``} >`; - // Skip clerk-ui script for headless variant - if (clerkJsVariant === 'headless') { + if (!shouldLoadClerkUi(env.ui)) { return clerkJsScript + '\n'; } const clerkUiScriptSrc = clerkUiScriptUrl({ - clerkUiUrl: getSafeEnv(locals).clerkUiUrl, + clerkUiUrl: typeof env.ui === 'object' ? env.ui.url : undefined, + clerkUiVersion: typeof env.ui === 'object' ? env.ui.version : undefined, domain, proxyUrl, publishableKey, diff --git a/packages/astro/src/server/get-safe-env.ts b/packages/astro/src/server/get-safe-env.ts index 7ec1824029b..453c6e656c9 100644 --- a/packages/astro/src/server/get-safe-env.ts +++ b/packages/astro/src/server/get-safe-env.ts @@ -21,6 +21,10 @@ function getContextEnvVar(envVarName: keyof InternalEnv, contextOrLocals: Contex * @internal */ function getSafeEnv(context: ContextOrLocals) { + const uiDisabled = getContextEnvVar('PUBLIC_CLERK_UI_DISABLED', context) === 'true'; + const uiUrl = getContextEnvVar('PUBLIC_CLERK_UI_URL', context); + const uiVersion = getContextEnvVar('PUBLIC_CLERK_UI_VERSION', context); + return { domain: getContextEnvVar('PUBLIC_CLERK_DOMAIN', context), isSatellite: getContextEnvVar('PUBLIC_CLERK_IS_SATELLITE', context) === 'true', @@ -31,9 +35,8 @@ function getSafeEnv(context: ContextOrLocals) { signInUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_IN_URL', context), signUpUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_UP_URL', context), clerkJsUrl: getContextEnvVar('PUBLIC_CLERK_JS_URL', context), - clerkUiUrl: getContextEnvVar('PUBLIC_CLERK_UI_URL', context), - clerkJsVariant: getContextEnvVar('PUBLIC_CLERK_JS_VARIANT', context) as 'headless' | '' | undefined, clerkJsVersion: getContextEnvVar('PUBLIC_CLERK_JS_VERSION', context), + ui: uiDisabled ? (false as const) : uiUrl || uiVersion ? { url: uiUrl, version: uiVersion } : undefined, apiVersion: getContextEnvVar('CLERK_API_VERSION', context), apiUrl: getContextEnvVar('CLERK_API_URL', context), telemetryDisabled: isTruthy(getContextEnvVar('PUBLIC_CLERK_TELEMETRY_DISABLED', context)), diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 7f0613e5968..37603a722da 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -30,12 +30,14 @@ type AstroClerkIntegrationParams = Without< MultiDomainAndOrProxyPrimitives & { appearance?: Appearance; clerkJSUrl?: string; - clerkJSVariant?: 'headless' | ''; clerkJSVersion?: string; /** - * The URL that `@clerk/ui` should be hot-loaded from. + * Controls loading of the `@clerk/ui` script. + * - `false` - Skip loading the UI (for custom UIs using Control Components) + * - `{ version?, url? }` - Load UI with specific version or URL + * - `undefined` (default) - Load UI normally */ - clerkUiUrl?: string; + ui?: false | { version?: string; url?: string }; }; type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & { diff --git a/packages/express/src/utils.ts b/packages/express/src/utils.ts index 92664fa8280..a97bd1cf3cf 100644 --- a/packages/express/src/utils.ts +++ b/packages/express/src/utils.ts @@ -8,11 +8,26 @@ export const requestHasAuthObject = (req: ExpressRequest): req is ExpressRequest }; export const loadClientEnv = () => { + // Build ui config from env vars + const uiDisabled = process.env.CLERK_UI_DISABLED === 'true'; + const uiUrl = process.env.CLERK_UI_URL; + const uiVersion = process.env.CLERK_UI_VERSION; + + const getUiConfig = (): false | { url?: string; version?: string } | undefined => { + if (uiDisabled) { + return false; + } + if (uiUrl || uiVersion) { + return { url: uiUrl, version: uiVersion }; + } + return undefined; + }; + return { publishableKey: process.env.CLERK_PUBLISHABLE_KEY || '', clerkJSUrl: process.env.CLERK_JS || process.env.CLERK_JS_URL || '', - clerkUiUrl: process.env.CLERK_UI_URL || '', clerkJSVersion: process.env.CLERK_JS_VERSION || '', + ui: getUiConfig(), }; }; diff --git a/packages/nextjs/src/pages/__tests__/index.test.tsx b/packages/nextjs/src/pages/__tests__/index.test.tsx index 071e36fcb57..d3cb3f3db95 100644 --- a/packages/nextjs/src/pages/__tests__/index.test.tsx +++ b/packages/nextjs/src/pages/__tests__/index.test.tsx @@ -17,14 +17,28 @@ describe('ClerkProvider', () => { }); }); - describe('clerkJSVariant', () => { + describe('ui', () => { const defaultProps = { children: '' }; - it('is either headless or empty', () => { - expectTypeOf({ ...defaultProps, clerkJSVariant: 'headless' as const }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, clerkJSVariant: '' as const }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, clerkJSVariant: undefined }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, clerkJSVariant: 'test' }).not.toMatchTypeOf(); + it('accepts false to disable UI loading', () => { + expectTypeOf({ ...defaultProps, ui: false as const }).toMatchTypeOf(); + }); + + it('accepts undefined for default UI loading', () => { + expectTypeOf({ ...defaultProps, ui: undefined }).toMatchTypeOf(); + }); + + it('accepts object with url and version options', () => { + expectTypeOf({ ...defaultProps, ui: { url: 'https://custom.com/ui.js' } }).toMatchTypeOf(); + expectTypeOf({ ...defaultProps, ui: { version: '1.0.0' } }).toMatchTypeOf(); + expectTypeOf({ + ...defaultProps, + ui: { url: 'https://custom.com/ui.js', version: '1.0.0' }, + }).toMatchTypeOf(); + }); + + it('accepts empty object', () => { + expectTypeOf({ ...defaultProps, ui: {} }).toMatchTypeOf(); }); }); diff --git a/packages/nextjs/src/utils/clerk-script.tsx b/packages/nextjs/src/utils/clerk-script.tsx index 22d4a088277..494966138ad 100644 --- a/packages/nextjs/src/utils/clerk-script.tsx +++ b/packages/nextjs/src/utils/clerk-script.tsx @@ -4,6 +4,7 @@ import { buildClerkUiScriptAttributes, clerkJsScriptUrl, clerkUiScriptUrl, + shouldLoadClerkUi, } from '@clerk/react/internal'; import NextScript from 'next/script'; import React from 'react'; @@ -43,7 +44,7 @@ function ClerkScript(props: ClerkScriptProps) { } export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) { - const { publishableKey, clerkJSUrl, clerkJSVersion, clerkJSVariant, nonce, clerkUiUrl, ui } = useClerkNextOptions(); + const { publishableKey, clerkJSUrl, clerkJSVersion, nonce, ui } = useClerkNextOptions(); const { domain, proxyUrl } = useClerk(); if (!publishableKey) { @@ -54,12 +55,11 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) publishableKey, clerkJSUrl, clerkJSVersion, - clerkJSVariant, nonce, domain, proxyUrl, - clerkUiVersion: ui?.version, - clerkUiUrl: ui?.url || clerkUiUrl, + clerkUiVersion: typeof ui === 'object' ? ui.version : undefined, + clerkUiUrl: typeof ui === 'object' ? ui.url : undefined, }; return ( @@ -70,7 +70,7 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) dataAttribute='data-clerk-js-script' router={router} /> - {clerkJSVariant !== 'headless' && ( + {shouldLoadClerkUi(ui) && ( ): any => { return { ...props, publishableKey: props.publishableKey || process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || '', clerkJSUrl: props.clerkJSUrl || process.env.NEXT_PUBLIC_CLERK_JS_URL, - clerkUiUrl: (props as any).clerkUiUrl || process.env.NEXT_PUBLIC_CLERK_UI_URL, clerkJSVersion: props.clerkJSVersion || process.env.NEXT_PUBLIC_CLERK_JS_VERSION, + ui: getUiConfigFromEnvAndProps(props.ui), proxyUrl: props.proxyUrl || process.env.NEXT_PUBLIC_CLERK_PROXY_URL || '', domain: props.domain || process.env.NEXT_PUBLIC_CLERK_DOMAIN || '', isSatellite: props.isSatellite || isTruthy(process.env.NEXT_PUBLIC_CLERK_IS_SATELLITE), diff --git a/packages/nuxt/src/global.d.ts b/packages/nuxt/src/global.d.ts index 7220a9070ab..b4b99f2bf7d 100644 --- a/packages/nuxt/src/global.d.ts +++ b/packages/nuxt/src/global.d.ts @@ -16,17 +16,12 @@ declare module 'nuxt/schema' { }; } interface PublicRuntimeConfig { - clerk: Omit & { + clerk: Omit & { /** * The URL that `@clerk/clerk-js` should be hot-loaded from. * Supports NUXT_PUBLIC_CLERK_JS_URL env var. */ jsUrl?: string; - /** - * The URL that `@clerk/ui` should be hot-loaded from. - * Supports NUXT_PUBLIC_CLERK_UI_URL env var. - */ - uiUrl?: string; }; } } diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 0f0fb72e6f0..d0db61a87d2 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -64,12 +64,12 @@ export default defineNuxtModule({ signUpForceRedirectUrl: options.signUpForceRedirectUrl, signUpUrl: options.signUpUrl, domain: options.domain, - // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUiUrl to support - // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. + // Using jsUrl instead of clerkJSUrl to support + // NUXT_PUBLIC_CLERK_JS_URL env vars. jsUrl: options.clerkJSUrl, - uiUrl: options.clerkUiUrl, - clerkJSVariant: options.clerkJSVariant, clerkJSVersion: options.clerkJSVersion, + // UI config: can be false, { url?, version? }, or undefined + ui: options.ui, isSatellite: options.isSatellite, // Backend specific variables that are safe to share. // We want them to be overridable like the other public keys (e.g NUXT_PUBLIC_CLERK_PROXY_URL) diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index 8879860afb6..41b743e7078 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -21,9 +21,8 @@ export default defineNuxtPlugin(nuxtApp => { nuxtApp.vueApp.use(clerkPlugin as any, { ...clerkConfig, - // Map jsUrl/uiUrl to clerkJSUrl/clerkUiUrl as expected by the Vue plugin + // Map jsUrl to clerkJSUrl as expected by the Vue plugin clerkJSUrl: clerkConfig.jsUrl, - clerkUiUrl: clerkConfig.uiUrl, sdkMetadata: { name: PACKAGE_NAME, version: PACKAGE_VERSION, diff --git a/packages/react-router/src/client/ReactRouterClerkProvider.tsx b/packages/react-router/src/client/ReactRouterClerkProvider.tsx index 33ea4406868..47da642a223 100644 --- a/packages/react-router/src/client/ReactRouterClerkProvider.tsx +++ b/packages/react-router/src/client/ReactRouterClerkProvider.tsx @@ -62,8 +62,8 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv __signInFallbackRedirectUrl, __signUpFallbackRedirectUrl, __clerkJSUrl, - __clerkUiUrl, __clerkJSVersion, + __ui, __telemetryDisabled, __telemetryDebug, } = clerkState?.__internal_clerk_state || {}; @@ -90,8 +90,8 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv signInFallbackRedirectUrl: __signInFallbackRedirectUrl, signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl, clerkJSUrl: __clerkJSUrl, - clerkUiUrl: __clerkUiUrl, clerkJSVersion: __clerkJSVersion, + ui: __ui, telemetry: { disabled: __telemetryDisabled, debug: __telemetryDebug, @@ -99,13 +99,13 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv }; return ( - + awaitableNavigateRef.current?.(to)} routerReplace={(to: string) => awaitableNavigateRef.current?.(to, { replace: true })} initialState={__clerk_ssr_state} sdkMetadata={SDK_METADATA} - {...mergedProps} + {...(mergedProps as any)} {...restProps} > {children} diff --git a/packages/react-router/src/client/types.ts b/packages/react-router/src/client/types.ts index 63f07aea3c4..cc379c82f61 100644 --- a/packages/react-router/src/client/types.ts +++ b/packages/react-router/src/client/types.ts @@ -19,8 +19,8 @@ export type ClerkState = { __signUpFallbackRedirectUrl: string | undefined; __clerk_debug: any; __clerkJSUrl: string | undefined; - __clerkUiUrl: string | undefined; __clerkJSVersion: string | undefined; + __ui: false | { url?: string; version?: string } | undefined; __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; }; diff --git a/packages/react-router/src/server/utils.ts b/packages/react-router/src/server/utils.ts index 1b0bd9a9fe7..80a7529c0fd 100644 --- a/packages/react-router/src/server/utils.ts +++ b/packages/react-router/src/server/utils.ts @@ -79,6 +79,7 @@ export const injectRequestStateIntoResponse = async ( */ export function getResponseClerkState(requestState: RequestStateWithRedirectUrls, context: AppLoadContext) { const { reason, message, isSignedIn, ...rest } = requestState; + const envVars = getPublicEnvVariables(context); const clerkState = wrapWithClerkState({ __clerk_ssr_state: rest.toAuth(), __publishableKey: requestState.publishableKey, @@ -92,11 +93,11 @@ export function getResponseClerkState(requestState: RequestStateWithRedirectUrls __signInFallbackRedirectUrl: requestState.signInFallbackRedirectUrl, __signUpFallbackRedirectUrl: requestState.signUpFallbackRedirectUrl, __clerk_debug: debugRequestState(requestState), - __clerkJSUrl: getPublicEnvVariables(context).clerkJsUrl, - __clerkUiUrl: getPublicEnvVariables(context).clerkUiUrl, - __clerkJSVersion: getPublicEnvVariables(context).clerkJsVersion, - __telemetryDisabled: getPublicEnvVariables(context).telemetryDisabled, - __telemetryDebug: getPublicEnvVariables(context).telemetryDebug, + __clerkJSUrl: envVars.clerkJsUrl, + __clerkJSVersion: envVars.clerkJsVersion, + __ui: envVars.ui, + __telemetryDisabled: envVars.telemetryDisabled, + __telemetryDebug: envVars.telemetryDebug, }); return { diff --git a/packages/react-router/src/utils/env.ts b/packages/react-router/src/utils/env.ts index 6c03570a7b2..7afc4f36a75 100644 --- a/packages/react-router/src/utils/env.ts +++ b/packages/react-router/src/utils/env.ts @@ -7,6 +7,21 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { return getEnvVariable(`VITE_${name}`, context) || getEnvVariable(name, context); }; + // Build ui config from env vars + const uiDisabled = getValue('CLERK_UI_DISABLED') === 'true'; + const uiUrl = getValue('CLERK_UI_URL'); + const uiVersion = getValue('CLERK_UI_VERSION'); + + const getUiConfig = (): false | { url?: string; version?: string } | undefined => { + if (uiDisabled) { + return false; + } + if (uiUrl || uiVersion) { + return { url: uiUrl || undefined, version: uiVersion || undefined }; + } + return undefined; + }; + return { publishableKey: getValue('CLERK_PUBLISHABLE_KEY'), domain: getValue('CLERK_DOMAIN'), @@ -15,9 +30,8 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { signInUrl: getValue('CLERK_SIGN_IN_URL'), signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL'), - clerkUiUrl: getValue('CLERK_UI_URL'), - clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined, clerkJsVersion: getValue('CLERK_JS_VERSION'), + ui: getUiConfig(), telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), signInForceRedirectUrl: getValue('CLERK_SIGN_IN_FORCE_REDIRECT_URL'), diff --git a/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx b/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx index 014117e332c..2d3cf823453 100644 --- a/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx +++ b/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx @@ -35,14 +35,28 @@ describe('ClerkProvider', () => { }); }); - describe('clerkJSVariant', () => { + describe('ui', () => { const defaultProps = { publishableKey: 'test', children: '' }; - it('is either headless or empty', () => { - expectTypeOf({ ...defaultProps, clerkJSVariant: 'headless' as const }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, clerkJSVariant: '' as const }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, clerkJSVariant: undefined }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, clerkJSVariant: 'test' }).not.toMatchTypeOf(); + it('accepts false to disable UI loading', () => { + expectTypeOf({ ...defaultProps, ui: false as const }).toMatchTypeOf(); + }); + + it('accepts undefined for default UI loading', () => { + expectTypeOf({ ...defaultProps, ui: undefined }).toMatchTypeOf(); + }); + + it('accepts object with url and version options', () => { + expectTypeOf({ ...defaultProps, ui: { url: 'https://custom.com/ui.js' } }).toMatchTypeOf(); + expectTypeOf({ ...defaultProps, ui: { version: '1.0.0' } }).toMatchTypeOf(); + expectTypeOf({ + ...defaultProps, + ui: { url: 'https://custom.com/ui.js', version: '1.0.0' }, + }).toMatchTypeOf(); + }); + + it('accepts empty object', () => { + expectTypeOf({ ...defaultProps, ui: {} }).toMatchTypeOf(); }); }); diff --git a/packages/react/src/internal.ts b/packages/react/src/internal.ts index 26e71d2e998..59bef920d40 100644 --- a/packages/react/src/internal.ts +++ b/packages/react/src/internal.ts @@ -9,6 +9,7 @@ export { clerkUiScriptUrl, buildClerkUiScriptAttributes, setClerkJsLoadingErrorPackageName, + shouldLoadClerkUi, } from '@clerk/shared/loadClerkJsScript'; export type { Ui } from '@clerk/ui/internal'; diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b9e0064dc1c..8c79f1d0878 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -1,6 +1,6 @@ import { inBrowser } from '@clerk/shared/browser'; import { clerkEvents, createClerkEventBus } from '@clerk/shared/clerkEventBus'; -import { loadClerkJsScript, loadClerkUiScript } from '@clerk/shared/loadClerkJsScript'; +import { loadClerkJsScript, loadClerkUiScript, shouldLoadClerkUi } from '@clerk/shared/loadClerkJsScript'; import type { __internal_AttemptToEnableEnvironmentSettingParams, __internal_AttemptToEnableEnvironmentSettingResult, @@ -509,8 +509,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } private async getClerkUiEntryChunk(): Promise { - // Skip UI loading for headless variant - if (this.options.clerkJSVariant === 'headless') { + if (!shouldLoadClerkUi(this.options.ui)) { return undefined; } @@ -518,10 +517,12 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { return this.options.clerkUiCtor; } + const uiOptions = this.options.ui && this.options.ui !== false ? this.options.ui : {}; + await loadClerkUiScript({ ...this.options, - clerkUiVersion: this.options.ui?.version, - clerkUiUrl: this.options.ui?.url || this.options.clerkUiUrl, + clerkUiVersion: uiOptions.version, + clerkUiUrl: uiOptions.url, publishableKey: this.#publishableKey, proxyUrl: this.proxyUrl, domain: this.domain, diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index 27146ec1197..de147bb54cd 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -11,6 +11,7 @@ import { loadClerkJsScript, loadClerkUiScript, setClerkJsLoadingErrorPackageName, + shouldLoadClerkUi, } from '../loadClerkJsScript'; import { loadScript } from '../loadScript'; import { getMajorVersion } from '../versionSelector'; @@ -165,13 +166,6 @@ describe('clerkJsScriptUrl()', () => { expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); }); - test('clerkJSVariant does not affect URL (headless build removed)', () => { - const result = clerkJsScriptUrl({ publishableKey: mockProdPublishableKey, clerkJSVariant: 'headless' }); - // The separate headless build has been removed - always returns standard browser bundle - // clerkJSVariant is still used to control UI loading, but not the clerk-js URL - expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); - }); - test('uses provided clerkJSVersion', () => { const result = clerkJsScriptUrl({ publishableKey: mockDevPublishableKey, clerkJSVersion: '6' }); expect(result).toContain('/npm/@clerk/clerk-js@6/'); @@ -470,3 +464,29 @@ describe('buildClerkUiScriptAttributes()', () => { expect(buildClerkUiScriptAttributes(input)).toEqual(expected); }); }); + +describe('shouldLoadClerkUi()', () => { + test('returns true when ui is undefined', () => { + expect(shouldLoadClerkUi(undefined)).toBe(true); + }); + + test('returns false when ui is false', () => { + expect(shouldLoadClerkUi(false)).toBe(false); + }); + + test('returns true when ui has url option', () => { + expect(shouldLoadClerkUi({ url: 'https://custom.com/ui.js' })).toBe(true); + }); + + test('returns true when ui has version option', () => { + expect(shouldLoadClerkUi({ version: '1.0.0' })).toBe(true); + }); + + test('returns true when ui has both url and version options', () => { + expect(shouldLoadClerkUi({ url: 'https://custom.com/ui.js', version: '1.0.0' })).toBe(true); + }); + + test('returns true when ui is an empty object', () => { + expect(shouldLoadClerkUi({})).toBe(true); + }); +}); diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index dec0d12aaea..5e52ced7d6f 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -13,12 +13,6 @@ const errorThrower = buildErrorThrower({ packageName: '@clerk/shared' }); export type LoadClerkJsScriptOptions = { publishableKey: string; clerkJSUrl?: string; - /** - * Set to `'headless'` to skip loading the `@clerk/ui` script. - * Note: The separate headless build of clerk-js has been removed as it's - * now identical to the standard build. This option only controls UI loading. - */ - clerkJSVariant?: 'headless' | ''; clerkJSVersion?: string; sdkMetadata?: SDKMetadata; proxyUrl?: string; @@ -32,6 +26,15 @@ export type LoadClerkJsScriptOptions = { scriptLoadTimeout?: number; }; +/** + * Determines whether the Clerk UI should be loaded based on the `ui` option. + * @param ui - The ui option from ClerkProvider/options + * @returns `true` if UI should be loaded, `false` if it should be skipped + */ +export const shouldLoadClerkUi = (ui: false | { version?: string; url?: string } | undefined): boolean => { + return ui !== false; +}; + export type LoadClerkUiScriptOptions = { publishableKey: string; clerkUiUrl?: string; diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index d2f9e7ef680..68d5a0d0ced 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2397,19 +2397,10 @@ export type IsomorphicClerkOptions = Without & { * The URL that `@clerk/clerk-js` should be hot-loaded from. */ clerkJSUrl?: string; - /** - * Set to `'headless'` to skip loading the `@clerk/ui` script. Use this when building - * custom UIs with Control Components and you don't need the prebuilt Clerk UI. - */ - clerkJSVariant?: 'headless' | ''; /** * The npm version for `@clerk/clerk-js`. */ clerkJSVersion?: string; - /** - * The URL that `@clerk/ui` should be hot-loaded from. - */ - clerkUiUrl?: string; /** * The Clerk Publishable Key for your instance. This can be found on the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard. */ @@ -2419,11 +2410,12 @@ export type IsomorphicClerkOptions = Without & { */ nonce?: string; /** - * @internal - * This is a structural-only type for the `ui` object that can be passed - * to Clerk.load() and ClerkProvider + * Controls loading of the `@clerk/ui` script. + * - `false` - Skip loading the UI (for custom UIs using Control Components) + * - `{ version?, url? }` - Load UI with specific version or URL + * - `undefined` (default) - Load UI normally */ - ui?: { version: string; url?: string }; + ui?: false | { version?: string; url?: string }; } & MultiDomainAndOrProxy; export interface LoadedClerk extends Clerk { diff --git a/packages/tanstack-react-start/src/client/types.ts b/packages/tanstack-react-start/src/client/types.ts index 49e3a71c1b2..7554cab301e 100644 --- a/packages/tanstack-react-start/src/client/types.ts +++ b/packages/tanstack-react-start/src/client/types.ts @@ -17,8 +17,8 @@ export type ClerkState = { __afterSignUpUrl: string | undefined; __clerk_debug: any; __clerkJSUrl: string | undefined; - __clerkUiUrl: string | undefined; __clerkJSVersion: string | undefined; + __ui: false | { url?: string; version?: string } | undefined; __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; }; diff --git a/packages/tanstack-react-start/src/client/utils.ts b/packages/tanstack-react-start/src/client/utils.ts index 3798f1b212f..2bf93ae4de2 100644 --- a/packages/tanstack-react-start/src/client/utils.ts +++ b/packages/tanstack-react-start/src/client/utils.ts @@ -19,7 +19,6 @@ export const pickFromClerkInitState = ( __signInUrl, __signUpUrl, __clerkJSUrl, - __clerkUiUrl, __clerkJSVersion, __telemetryDisabled, __telemetryDebug, @@ -29,6 +28,7 @@ export const pickFromClerkInitState = ( __signUpFallbackRedirectUrl, __keylessClaimUrl, __keylessApiKeysUrl, + __ui, } = clerkInitState || {}; return { @@ -40,8 +40,8 @@ export const pickFromClerkInitState = ( signInUrl: __signInUrl, signUpUrl: __signUpUrl, clerkJSUrl: __clerkJSUrl, - clerkUiUrl: __clerkUiUrl, clerkJSVersion: __clerkJSVersion, + ui: __ui, telemetry: { disabled: __telemetryDisabled, debug: __telemetryDebug, @@ -56,17 +56,17 @@ export const pickFromClerkInitState = ( }; export const mergeWithPublicEnvs = (restInitState: any) => { + const envVars = getPublicEnvVariables(); return { ...restInitState, - publishableKey: restInitState.publishableKey || getPublicEnvVariables().publishableKey, - domain: restInitState.domain || getPublicEnvVariables().domain, - isSatellite: restInitState.isSatellite || getPublicEnvVariables().isSatellite, - signInUrl: restInitState.signInUrl || getPublicEnvVariables().signInUrl, - signUpUrl: restInitState.signUpUrl || getPublicEnvVariables().signUpUrl, - clerkJSUrl: restInitState.clerkJSUrl || getPublicEnvVariables().clerkJsUrl, - clerkUiUrl: restInitState.clerkUiUrl || getPublicEnvVariables().clerkUiUrl, - clerkJSVersion: restInitState.clerkJSVersion || getPublicEnvVariables().clerkJsVersion, + publishableKey: restInitState.publishableKey || envVars.publishableKey, + domain: restInitState.domain || envVars.domain, + isSatellite: restInitState.isSatellite || envVars.isSatellite, + signInUrl: restInitState.signInUrl || envVars.signInUrl, + signUpUrl: restInitState.signUpUrl || envVars.signUpUrl, + clerkJSUrl: restInitState.clerkJSUrl || envVars.clerkJsUrl, + clerkJSVersion: restInitState.clerkJSVersion || envVars.clerkJsVersion, signInForceRedirectUrl: restInitState.signInForceRedirectUrl, - clerkJSVariant: restInitState.clerkJSVariant || getPublicEnvVariables().clerkJsVariant, + ui: restInitState.ui ?? envVars.ui, }; }; diff --git a/packages/tanstack-react-start/src/server/constants.ts b/packages/tanstack-react-start/src/server/constants.ts index 7ef5ec26b8c..68670c65579 100644 --- a/packages/tanstack-react-start/src/server/constants.ts +++ b/packages/tanstack-react-start/src/server/constants.ts @@ -10,7 +10,7 @@ export const commonEnvs = () => { // Public environment variables CLERK_JS_VERSION: publicEnvs.clerkJsVersion, CLERK_JS_URL: publicEnvs.clerkJsUrl, - CLERK_UI_URL: publicEnvs.clerkUiUrl, + UI: publicEnvs.ui, PUBLISHABLE_KEY: publicEnvs.publishableKey, DOMAIN: publicEnvs.domain, PROXY_URL: publicEnvs.proxyUrl, diff --git a/packages/tanstack-react-start/src/server/utils/index.ts b/packages/tanstack-react-start/src/server/utils/index.ts index 780ea1d8e79..5a7961216d3 100644 --- a/packages/tanstack-react-start/src/server/utils/index.ts +++ b/packages/tanstack-react-start/src/server/utils/index.ts @@ -20,6 +20,19 @@ export const wrapWithClerkState = (data: any) => { * * @internal */ +function getUiConfigFromEnv(): false | { url?: string; version?: string } | undefined { + const uiDisabled = getEnvVariable('CLERK_UI_DISABLED') === 'true'; + if (uiDisabled) { + return false; + } + const uiUrl = getEnvVariable('CLERK_UI_URL'); + const uiVersion = getEnvVariable('CLERK_UI_VERSION'); + if (uiUrl || uiVersion) { + return { url: uiUrl || undefined, version: uiVersion || undefined }; + } + return undefined; +} + export function getResponseClerkState(requestState: RequestState, additionalStateOptions: AdditionalStateOptions = {}) { const { reason, message, isSignedIn, ...rest } = requestState; @@ -35,8 +48,8 @@ export function getResponseClerkState(requestState: RequestState, additionalStat __afterSignUpUrl: requestState.afterSignUpUrl, __clerk_debug: debugRequestState(requestState), __clerkJSUrl: getEnvVariable('CLERK_JS') || getEnvVariable('CLERK_JS_URL'), - __clerkUiUrl: getEnvVariable('CLERK_UI_URL'), __clerkJSVersion: getEnvVariable('CLERK_JS_VERSION'), + __ui: getUiConfigFromEnv(), __telemetryDisabled: isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')), __telemetryDebug: isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')), __signInForceRedirectUrl: diff --git a/packages/tanstack-react-start/src/utils/env.ts b/packages/tanstack-react-start/src/utils/env.ts index bb8b008a0ee..58610027234 100644 --- a/packages/tanstack-react-start/src/utils/env.ts +++ b/packages/tanstack-react-start/src/utils/env.ts @@ -6,6 +6,21 @@ export const getPublicEnvVariables = () => { return getEnvVariable(`VITE_${name}`) || getEnvVariable(name); }; + // Build ui config from env vars + const uiDisabled = getValue('CLERK_UI_DISABLED') === 'true'; + const uiUrl = getValue('CLERK_UI_URL'); + const uiVersion = getValue('CLERK_UI_VERSION'); + + const getUiConfig = (): false | { url?: string; version?: string } | undefined => { + if (uiDisabled) { + return false; + } + if (uiUrl || uiVersion) { + return { url: uiUrl || undefined, version: uiVersion || undefined }; + } + return undefined; + }; + return { publishableKey: getValue('CLERK_PUBLISHABLE_KEY'), domain: getValue('CLERK_DOMAIN'), @@ -14,9 +29,8 @@ export const getPublicEnvVariables = () => { signInUrl: getValue('CLERK_SIGN_IN_URL'), signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'), - clerkUiUrl: getValue('CLERK_UI_URL'), - clerkJsVariant: getValue('CLERK_JS_VARIANT') as '' | 'headless' | undefined, clerkJsVersion: getValue('CLERK_JS_VERSION'), + ui: getUiConfig(), telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'), diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index f6ed9933884..29f237d85e7 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -1,6 +1,11 @@ import { inBrowser } from '@clerk/shared/browser'; import { deriveState } from '@clerk/shared/deriveState'; -import { loadClerkJsScript, type LoadClerkJsScriptOptions, loadClerkUiScript } from '@clerk/shared/loadClerkJsScript'; +import { + loadClerkJsScript, + type LoadClerkJsScriptOptions, + loadClerkUiScript, + shouldLoadClerkUi, +} from '@clerk/shared/loadClerkJsScript'; import type { Clerk, ClerkOptions, @@ -78,19 +83,22 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { void (async () => { try { const clerkPromise = loadClerkJsScript(options); - // Skip UI loading for headless variant - const clerkUiCtorPromise = - pluginOptions.clerkJSVariant === 'headless' - ? 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; - })(); + // Skip UI loading when ui={false} + const clerkUiCtorPromise = !shouldLoadClerkUi(pluginOptions.ui) + ? Promise.resolve(undefined) + : pluginOptions.clerkUiCtor + ? Promise.resolve(pluginOptions.clerkUiCtor) + : (async () => { + await loadClerkUiScript({ + ...options, + clerkUiUrl: typeof pluginOptions.ui === 'object' ? pluginOptions.ui.url : undefined, + clerkUiVersion: typeof pluginOptions.ui === 'object' ? pluginOptions.ui.version : undefined, + }); + if (!window.__internal_ClerkUiCtor) { + throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); + } + return window.__internal_ClerkUiCtor; + })(); await clerkPromise; From 1ca99cb4fc99b12be1507b023fdc76b77126d27a Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 09:11:20 -0600 Subject: [PATCH 10/30] fix: update integration templates and tests to use ui prop --- integration/templates/next-app-router/src/app/layout.tsx | 2 +- integration/tests/headless-variant.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx index 2db280398a6..0e54140718d 100644 --- a/integration/templates/next-app-router/src/app/layout.tsx +++ b/integration/templates/next-app-router/src/app/layout.tsx @@ -12,7 +12,7 @@ export const metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( { +test.describe('ui disabled @nextjs', () => { test.describe.configure({ mode: 'serial' }); let app: Application; test.beforeAll(async () => { app = await appConfigs.next.appRouter.clone().commit(); await app.setup(); - // Use withEmailCodes but add the headless variant - const env = appConfigs.envs.withEmailCodes.clone().setEnvVariable('public', 'CLERK_JS_VARIANT', 'headless'); + // Use withEmailCodes but disable the UI + const env = appConfigs.envs.withEmailCodes.clone().setEnvVariable('public', 'CLERK_UI_DISABLED', 'true'); await app.withEnv(env); await app.dev(); }); @@ -20,7 +20,7 @@ test.describe('headless variant @nextjs', () => { await app.teardown(); }); - test('does not inject clerk-ui script when headless variant is used', async ({ page }) => { + test('does not inject clerk-ui script when ui is disabled', async ({ page }) => { await page.goto(app.serverUrl); // Wait for clerk-js script to be present (ensures page has loaded) From eee7409f2936bb7a59f131f6f81ddbe5ac68f46c Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 09:26:00 -0600 Subject: [PATCH 11/30] fix: update expo and chrome-extension for headless removal --- .../chrome-extension/src/background/clerk.ts | 2 +- packages/chrome-extension/src/internal/clerk.ts | 8 +------- .../src/internal/utils/request-handler.ts | 2 +- .../src/internal/utils/response-handler.ts | 2 +- .../src/react/ClerkProvider.tsx | 2 +- .../provider/singleton/createClerkInstance.ts | 17 +++++++++++++++-- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/chrome-extension/src/background/clerk.ts b/packages/chrome-extension/src/background/clerk.ts index 57c68d5097d..b95ca2f850c 100644 --- a/packages/chrome-extension/src/background/clerk.ts +++ b/packages/chrome-extension/src/background/clerk.ts @@ -1,4 +1,4 @@ -import type { Clerk } from '@clerk/clerk-js/no-rhc'; +import type { Clerk } from '@clerk/clerk-js'; import { createClerkClient as _createClerkClient, diff --git a/packages/chrome-extension/src/internal/clerk.ts b/packages/chrome-extension/src/internal/clerk.ts index bcd6362a529..60e4a04c413 100644 --- a/packages/chrome-extension/src/internal/clerk.ts +++ b/packages/chrome-extension/src/internal/clerk.ts @@ -1,4 +1,4 @@ -import { Clerk } from '@clerk/clerk-js/no-rhc'; +import { Clerk } from '@clerk/clerk-js'; import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; import { parsePublishableKey } from '@clerk/shared/keys'; import browser from 'webextension-polyfill'; @@ -35,12 +35,6 @@ export function createClerkClient({ storageCache = BrowserStorageCache, syncHost, }: CreateClerkClientOptions) { - if (scope === SCOPE.BACKGROUND) { - // TODO @nikos - // @ts-expect-error will be replaced by clerk ui - Clerk.mountComponentRenderer = undefined; - } - // Don't cache background scripts as it can result in out-of-sync client information. if (clerk && scope !== SCOPE.BACKGROUND) { return clerk; diff --git a/packages/chrome-extension/src/internal/utils/request-handler.ts b/packages/chrome-extension/src/internal/utils/request-handler.ts index 448755df3bf..60cde36adc3 100644 --- a/packages/chrome-extension/src/internal/utils/request-handler.ts +++ b/packages/chrome-extension/src/internal/utils/request-handler.ts @@ -7,7 +7,7 @@ type Handler = Parameters[0]; type Req = Parameters[0]; /** Append the JWT to the FAPI request */ -export function requestHandler(jwtHandler: JWTHandler, { isProd }: { isProd: boolean }) { +export function requestHandler(jwtHandler: JWTHandler, { isProd }: { isProd: boolean }): Handler { const handler: Handler = async requestInit => { requestInit.credentials = 'omit'; diff --git a/packages/chrome-extension/src/internal/utils/response-handler.ts b/packages/chrome-extension/src/internal/utils/response-handler.ts index 9a5c161e952..7bcf3a16e4b 100644 --- a/packages/chrome-extension/src/internal/utils/response-handler.ts +++ b/packages/chrome-extension/src/internal/utils/response-handler.ts @@ -7,7 +7,7 @@ type Handler = Parameters[0]; type Res = Parameters[1]; /** Retrieve the JWT to the FAPI response */ -export function responseHandler(jwtHandler: JWTHandler, { isProd }: { isProd: boolean }) { +export function responseHandler(jwtHandler: JWTHandler, { isProd }: { isProd: boolean }): Handler { const handler: Handler = async (_, response) => { if (isProd) { await prodHandler(response, jwtHandler); diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index 45237484496..04905ee0fbf 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -1,4 +1,4 @@ -import type { Clerk } from '@clerk/clerk-js/no-rhc'; +import type { Clerk } from '@clerk/clerk-js'; import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/react'; import { ClerkProvider as ClerkReactProvider } from '@clerk/react'; import type { Ui } from '@clerk/react/internal'; diff --git a/packages/expo/src/provider/singleton/createClerkInstance.ts b/packages/expo/src/provider/singleton/createClerkInstance.ts index 00ea7127ad0..f41208d9388 100644 --- a/packages/expo/src/provider/singleton/createClerkInstance.ts +++ b/packages/expo/src/provider/singleton/createClerkInstance.ts @@ -1,4 +1,3 @@ -import type { FapiRequestInit, FapiResponse } from '@clerk/clerk-js/dist/types/core/fapiClient'; import { type Clerk, isClerkRuntimeError } from '@clerk/clerk-js'; import type { BrowserClerk, HeadlessBrowserClerk } from '@clerk/react'; import { is4xxError } from '@clerk/shared/error'; @@ -23,6 +22,20 @@ import { errorThrower } from '../../errorThrower'; import { isNative } from '../../utils'; import type { BuildClerkOptions } from './types'; +/** + * Internal types for FAPI client callbacks. + * These are simplified versions of the internal clerk-js types, + * used only for the __internal_onBeforeRequest and __internal_onAfterResponse hooks. + */ +type FapiRequestInit = RequestInit & { + url?: URL; + headers?: Headers; +}; + +type FapiResponse = Response & { + payload: { errors?: Array<{ code: string }> } | null; +}; + const KEY = '__clerk_client_jwt'; let __internal_clerk: HeadlessBrowserClerk | BrowserClerk | undefined; @@ -168,7 +181,7 @@ export function createClerkInstance(ClerkClass: typeof Clerk) { let nativeApiErrorShown = false; // @ts-expect-error - This is an internal API - __internal_clerk.__internal_onAfterResponse(async (_: FapiRequestInit, response: FapiResponse) => { + __internal_clerk.__internal_onAfterResponse(async (_: FapiRequestInit, response: FapiResponse) => { const authHeader = response.headers.get('authorization'); if (authHeader) { await saveToken(KEY, authHeader); From b85a057f80adc13548166893c6c395be8e567620 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 09:28:55 -0600 Subject: [PATCH 12/30] fix: restore /no-rhc export for chrome-extension CSP compliance --- packages/chrome-extension/src/background/clerk.ts | 2 +- packages/chrome-extension/src/internal/clerk.ts | 2 +- packages/chrome-extension/src/react/ClerkProvider.tsx | 2 +- packages/clerk-js/package.json | 10 ++++++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/chrome-extension/src/background/clerk.ts b/packages/chrome-extension/src/background/clerk.ts index b95ca2f850c..57c68d5097d 100644 --- a/packages/chrome-extension/src/background/clerk.ts +++ b/packages/chrome-extension/src/background/clerk.ts @@ -1,4 +1,4 @@ -import type { Clerk } from '@clerk/clerk-js'; +import type { Clerk } from '@clerk/clerk-js/no-rhc'; import { createClerkClient as _createClerkClient, diff --git a/packages/chrome-extension/src/internal/clerk.ts b/packages/chrome-extension/src/internal/clerk.ts index 60e4a04c413..3cd2eb4044d 100644 --- a/packages/chrome-extension/src/internal/clerk.ts +++ b/packages/chrome-extension/src/internal/clerk.ts @@ -1,4 +1,4 @@ -import { Clerk } from '@clerk/clerk-js'; +import { Clerk } from '@clerk/clerk-js/no-rhc'; import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; import { parsePublishableKey } from '@clerk/shared/keys'; import browser from 'webextension-polyfill'; diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index 04905ee0fbf..45237484496 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -1,4 +1,4 @@ -import type { Clerk } from '@clerk/clerk-js'; +import type { Clerk } from '@clerk/clerk-js/no-rhc'; import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/react'; import { ClerkProvider as ClerkReactProvider } from '@clerk/react'; import type { Ui } from '@clerk/react/internal'; diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 441096ac92c..3c11fe23279 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -39,6 +39,16 @@ "types": "./dist/types/index.d.ts", "default": "./dist/clerk.js" } + }, + "./no-rhc": { + "import": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.no-rhc.mjs" + }, + "require": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.no-rhc.js" + } } }, "files": [ From f3a8681646957a33c25d801e128abed43ef284fd Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 10:03:30 -0600 Subject: [PATCH 13/30] fix: add false-cjs to attw ignore rules for exports field --- packages/clerk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 3c11fe23279..6f5db6c28bf 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -72,7 +72,7 @@ "format": "node ../../scripts/format-package.mjs", "format:check": "node ../../scripts/format-package.mjs --check", "lint": "eslint src", - "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports", + "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports --ignore-rules false-cjs", "lint:publint": "publint || true", "postbuild:disabled": "node ../../scripts/search-for-rhc.mjs file dist/clerk.no-rhc.mjs", "test": "vitest --watch=false", From a608376a2d7e22163a77cabbeac79b82d990eefe Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 12:55:58 -0600 Subject: [PATCH 14/30] feat: rename ui prop to prefetchUI for disabling UI bundle prefetching --- .../next-app-router/src/app/layout.tsx | 2 +- integration/tests/headless-variant.test.ts | 8 ++--- packages/astro/src/env.d.ts | 4 +-- .../src/integration/create-integration.ts | 6 ++-- .../src/internal/create-clerk-instance.ts | 12 +++---- .../internal/merge-env-vars-with-params.ts | 34 ++++++------------- .../src/server/build-clerk-hotload-script.ts | 6 ++-- packages/astro/src/server/get-safe-env.ts | 6 ++-- packages/astro/src/types.ts | 9 +++-- packages/express/src/utils.ts | 18 ++-------- packages/nextjs/src/utils/clerk-script.tsx | 8 ++--- .../src/utils/mergeNextClerkPropsWithEnv.ts | 24 +++---------- .../src/client/ReactRouterClerkProvider.tsx | 4 +-- packages/react-router/src/client/types.ts | 2 +- packages/react-router/src/server/utils.ts | 2 +- packages/react-router/src/utils/env.ts | 18 ++-------- packages/react/src/internal.ts | 2 +- packages/react/src/isomorphicClerk.ts | 8 ++--- .../src/__tests__/loadClerkJsScript.spec.ts | 28 +++++---------- packages/shared/src/loadClerkJsScript.ts | 10 +++--- packages/shared/src/types/clerk.ts | 9 +++-- .../tanstack-react-start/src/client/types.ts | 2 +- .../tanstack-react-start/src/client/utils.ts | 6 ++-- .../src/server/utils/index.ts | 15 +++----- .../tanstack-react-start/src/utils/env.ts | 18 ++-------- packages/vue/src/plugin.ts | 12 +++---- 26 files changed, 84 insertions(+), 189 deletions(-) diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx index 0e54140718d..f931fe2c271 100644 --- a/integration/templates/next-app-router/src/app/layout.tsx +++ b/integration/templates/next-app-router/src/app/layout.tsx @@ -12,7 +12,7 @@ export const metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( { +test.describe('prefetchUI disabled @nextjs', () => { test.describe.configure({ mode: 'serial' }); let app: Application; test.beforeAll(async () => { app = await appConfigs.next.appRouter.clone().commit(); await app.setup(); - // Use withEmailCodes but disable the UI - const env = appConfigs.envs.withEmailCodes.clone().setEnvVariable('public', 'CLERK_UI_DISABLED', 'true'); + // Use withEmailCodes but disable the UI prefetching + const env = appConfigs.envs.withEmailCodes.clone().setEnvVariable('public', 'CLERK_PREFETCH_UI_DISABLED', 'true'); await app.withEnv(env); await app.dev(); }); @@ -20,7 +20,7 @@ test.describe('ui disabled @nextjs', () => { await app.teardown(); }); - test('does not inject clerk-ui script when ui is disabled', async ({ page }) => { + test('does not inject clerk-ui script when prefetchUI is disabled', async ({ page }) => { await page.goto(app.serverUrl); // Wait for clerk-js script to be present (ensures page has loaded) diff --git a/packages/astro/src/env.d.ts b/packages/astro/src/env.d.ts index e7140423b2d..605af19942e 100644 --- a/packages/astro/src/env.d.ts +++ b/packages/astro/src/env.d.ts @@ -5,9 +5,7 @@ interface InternalEnv { readonly PUBLIC_CLERK_PUBLISHABLE_KEY?: string; readonly PUBLIC_CLERK_JS_URL?: string; readonly PUBLIC_CLERK_JS_VERSION?: string; - readonly PUBLIC_CLERK_UI_DISABLED?: string; - readonly PUBLIC_CLERK_UI_URL?: string; - readonly PUBLIC_CLERK_UI_VERSION?: string; + readonly PUBLIC_CLERK_PREFETCH_UI_DISABLED?: string; readonly CLERK_API_KEY?: string; readonly CLERK_API_URL?: string; readonly CLERK_API_VERSION?: string; diff --git a/packages/astro/src/integration/create-integration.ts b/packages/astro/src/integration/create-integration.ts index f1d6115aa29..dab761aedf8 100644 --- a/packages/astro/src/integration/create-integration.ts +++ b/packages/astro/src/integration/create-integration.ts @@ -21,7 +21,7 @@ function createIntegration() // These are not provided when the "bundled" integration is used const clerkJSUrl = (params as any)?.clerkJSUrl as string | undefined; const clerkJSVersion = (params as any)?.clerkJSVersion as string | undefined; - const ui = (params as any)?.ui as false | { version?: string; url?: string } | undefined; + const prefetchUI = (params as any)?.prefetchUI as boolean | undefined; return { name: '@clerk/astro/integration', @@ -58,9 +58,7 @@ function createIntegration() ...buildEnvVarFromOption(domain, 'PUBLIC_CLERK_DOMAIN'), ...buildEnvVarFromOption(clerkJSUrl, 'PUBLIC_CLERK_JS_URL'), ...buildEnvVarFromOption(clerkJSVersion, 'PUBLIC_CLERK_JS_VERSION'), - ...buildEnvVarFromOption(ui === false, 'PUBLIC_CLERK_UI_DISABLED'), - ...buildEnvVarFromOption(typeof ui === 'object' ? ui.url : undefined, 'PUBLIC_CLERK_UI_URL'), - ...buildEnvVarFromOption(typeof ui === 'object' ? ui.version : undefined, 'PUBLIC_CLERK_UI_VERSION'), + ...buildEnvVarFromOption(prefetchUI === false, 'PUBLIC_CLERK_PREFETCH_UI_DISABLED'), }, ssr: { diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index 7005953a472..fdb73d1fa6f 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -2,7 +2,7 @@ import { loadClerkJsScript, loadClerkUiScript, setClerkJsLoadingErrorPackageName, - shouldLoadClerkUi, + shouldPrefetchClerkUi, } from '@clerk/shared/loadClerkJsScript'; import type { ClerkOptions } from '@clerk/shared/types'; import type { ClerkUiConstructor } from '@clerk/shared/ui'; @@ -112,12 +112,12 @@ async function getClerkJsEntryChunk(options?: AstroClerkCre /** * Gets the ClerkUI constructor, either from options or by loading the script. * Returns early if window.__internal_ClerkUiCtor already exists. - * Returns undefined when ui={false} (no UI needed). + * Returns undefined when prefetchUI={false} (no UI needed). */ async function getClerkUiEntryChunk( options?: AstroClerkCreateInstanceParams, ): Promise { - if (!shouldLoadClerkUi(options?.ui)) { + if (!shouldPrefetchClerkUi(options?.prefetchUI)) { return undefined; } @@ -125,11 +125,7 @@ async function getClerkUiEntryChunk( return options.clerkUiCtor; } - await loadClerkUiScript({ - ...(options as any), - clerkUiUrl: typeof options?.ui === 'object' ? options.ui.url : undefined, - clerkUiVersion: typeof options?.ui === 'object' ? options.ui.version : undefined, - }); + await loadClerkUiScript(options as any); if (!window.__internal_ClerkUiCtor) { throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index e7277b8f4e6..4d813662d85 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -3,37 +3,23 @@ import { isTruthy } from '@clerk/shared/underscore'; import type { AstroClerkIntegrationParams } from '../types'; /** - * Merges `ui` param with env vars. - * - If param `ui` is explicitly `false`, return `false` - * - If env `PUBLIC_CLERK_UI_DISABLED` is "true", return `false` - * - Otherwise merge param and env values for url/version + * Merges `prefetchUI` param with env vars. + * - If param `prefetchUI` is explicitly `false`, return `false` + * - If env `PUBLIC_CLERK_PREFETCH_UI_DISABLED` is "true", return `false` + * - Otherwise return `undefined` (default behavior: prefetch UI) */ -function mergeUiConfig( - paramUi: AstroClerkIntegrationParams['ui'], -): false | { url?: string; version?: string } | undefined { +function mergePrefetchUIConfig(paramPrefetchUI: AstroClerkIntegrationParams['prefetchUI']): boolean | undefined { // Explicit false from param takes precedence - if (paramUi === false) { + if (paramPrefetchUI === false) { return false; } // Check env var for disabled - if (import.meta.env.PUBLIC_CLERK_UI_DISABLED === 'true') { + if (import.meta.env.PUBLIC_CLERK_PREFETCH_UI_DISABLED === 'true') { return false; } - const envUrl = import.meta.env.PUBLIC_CLERK_UI_URL; - const envVersion = import.meta.env.PUBLIC_CLERK_UI_VERSION; - - // Merge param values with env fallbacks - const url = paramUi?.url || envUrl; - const version = paramUi?.version || envVersion; - - // Return undefined if no values set, otherwise return config object - if (!url && !version) { - return undefined; - } - - return { url, version }; + return undefined; } /** @@ -50,7 +36,7 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish telemetry: paramTelemetry, clerkJSUrl: paramClerkJSUrl, clerkJSVersion: paramClerkJSVersion, - ui: paramUi, + prefetchUI: paramPrefetchUI, ...rest } = params || {}; @@ -63,7 +49,7 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish publishableKey: paramPublishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, clerkJSVersion: paramClerkJSVersion || import.meta.env.PUBLIC_CLERK_JS_VERSION, - ui: mergeUiConfig(paramUi), + prefetchUI: mergePrefetchUIConfig(paramPrefetchUI), telemetry: paramTelemetry || { disabled: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DISABLED), debug: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DEBUG), diff --git a/packages/astro/src/server/build-clerk-hotload-script.ts b/packages/astro/src/server/build-clerk-hotload-script.ts index 81d45aec59d..37dc2045933 100644 --- a/packages/astro/src/server/build-clerk-hotload-script.ts +++ b/packages/astro/src/server/build-clerk-hotload-script.ts @@ -1,4 +1,4 @@ -import { clerkJsScriptUrl, clerkUiScriptUrl, shouldLoadClerkUi } from '@clerk/shared/loadClerkJsScript'; +import { clerkJsScriptUrl, clerkUiScriptUrl, shouldPrefetchClerkUi } from '@clerk/shared/loadClerkJsScript'; import type { APIContext } from 'astro'; import { getSafeEnv } from './get-safe-env'; @@ -30,13 +30,11 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { ${domain ? `data-clerk-domain="${domain}"` : ``} >`; - if (!shouldLoadClerkUi(env.ui)) { + if (!shouldPrefetchClerkUi(env.prefetchUI)) { return clerkJsScript + '\n'; } const clerkUiScriptSrc = clerkUiScriptUrl({ - clerkUiUrl: typeof env.ui === 'object' ? env.ui.url : undefined, - clerkUiVersion: typeof env.ui === 'object' ? env.ui.version : undefined, domain, proxyUrl, publishableKey, diff --git a/packages/astro/src/server/get-safe-env.ts b/packages/astro/src/server/get-safe-env.ts index 453c6e656c9..7bf9948c02e 100644 --- a/packages/astro/src/server/get-safe-env.ts +++ b/packages/astro/src/server/get-safe-env.ts @@ -21,9 +21,7 @@ function getContextEnvVar(envVarName: keyof InternalEnv, contextOrLocals: Contex * @internal */ function getSafeEnv(context: ContextOrLocals) { - const uiDisabled = getContextEnvVar('PUBLIC_CLERK_UI_DISABLED', context) === 'true'; - const uiUrl = getContextEnvVar('PUBLIC_CLERK_UI_URL', context); - const uiVersion = getContextEnvVar('PUBLIC_CLERK_UI_VERSION', context); + const prefetchUIDisabled = getContextEnvVar('PUBLIC_CLERK_PREFETCH_UI_DISABLED', context) === 'true'; return { domain: getContextEnvVar('PUBLIC_CLERK_DOMAIN', context), @@ -36,7 +34,7 @@ function getSafeEnv(context: ContextOrLocals) { signUpUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_UP_URL', context), clerkJsUrl: getContextEnvVar('PUBLIC_CLERK_JS_URL', context), clerkJsVersion: getContextEnvVar('PUBLIC_CLERK_JS_VERSION', context), - ui: uiDisabled ? (false as const) : uiUrl || uiVersion ? { url: uiUrl, version: uiVersion } : undefined, + prefetchUI: prefetchUIDisabled ? false : undefined, apiVersion: getContextEnvVar('CLERK_API_VERSION', context), apiUrl: getContextEnvVar('CLERK_API_URL', context), telemetryDisabled: isTruthy(getContextEnvVar('PUBLIC_CLERK_TELEMETRY_DISABLED', context)), diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 37603a722da..67f8a932aa1 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -32,12 +32,11 @@ type AstroClerkIntegrationParams = Without< clerkJSUrl?: string; clerkJSVersion?: string; /** - * Controls loading of the `@clerk/ui` script. - * - `false` - Skip loading the UI (for custom UIs using Control Components) - * - `{ version?, url? }` - Load UI with specific version or URL - * - `undefined` (default) - Load UI normally + * Controls prefetching of the `@clerk/ui` script. + * - `false` - Skip prefetching the UI (for custom UIs using Control Components) + * - `undefined` (default) - Prefetch UI normally */ - ui?: false | { version?: string; url?: string }; + prefetchUI?: boolean; }; type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & { diff --git a/packages/express/src/utils.ts b/packages/express/src/utils.ts index a97bd1cf3cf..577a685cbf7 100644 --- a/packages/express/src/utils.ts +++ b/packages/express/src/utils.ts @@ -8,26 +8,14 @@ export const requestHasAuthObject = (req: ExpressRequest): req is ExpressRequest }; export const loadClientEnv = () => { - // Build ui config from env vars - const uiDisabled = process.env.CLERK_UI_DISABLED === 'true'; - const uiUrl = process.env.CLERK_UI_URL; - const uiVersion = process.env.CLERK_UI_VERSION; - - const getUiConfig = (): false | { url?: string; version?: string } | undefined => { - if (uiDisabled) { - return false; - } - if (uiUrl || uiVersion) { - return { url: uiUrl, version: uiVersion }; - } - return undefined; - }; + // Build prefetchUI config from env vars + const prefetchUIDisabled = process.env.CLERK_PREFETCH_UI_DISABLED === 'true'; return { publishableKey: process.env.CLERK_PUBLISHABLE_KEY || '', clerkJSUrl: process.env.CLERK_JS || process.env.CLERK_JS_URL || '', clerkJSVersion: process.env.CLERK_JS_VERSION || '', - ui: getUiConfig(), + prefetchUI: prefetchUIDisabled ? false : undefined, }; }; diff --git a/packages/nextjs/src/utils/clerk-script.tsx b/packages/nextjs/src/utils/clerk-script.tsx index 494966138ad..e649128fafb 100644 --- a/packages/nextjs/src/utils/clerk-script.tsx +++ b/packages/nextjs/src/utils/clerk-script.tsx @@ -4,7 +4,7 @@ import { buildClerkUiScriptAttributes, clerkJsScriptUrl, clerkUiScriptUrl, - shouldLoadClerkUi, + shouldPrefetchClerkUi, } from '@clerk/react/internal'; import NextScript from 'next/script'; import React from 'react'; @@ -44,7 +44,7 @@ function ClerkScript(props: ClerkScriptProps) { } export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) { - const { publishableKey, clerkJSUrl, clerkJSVersion, nonce, ui } = useClerkNextOptions(); + const { publishableKey, clerkJSUrl, clerkJSVersion, nonce, prefetchUI } = useClerkNextOptions(); const { domain, proxyUrl } = useClerk(); if (!publishableKey) { @@ -58,8 +58,6 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) nonce, domain, proxyUrl, - clerkUiVersion: typeof ui === 'object' ? ui.version : undefined, - clerkUiUrl: typeof ui === 'object' ? ui.url : undefined, }; return ( @@ -70,7 +68,7 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) dataAttribute='data-clerk-js-script' router={router} /> - {shouldLoadClerkUi(ui) && ( + {shouldPrefetchClerkUi(prefetchUI) && ( ({ children, ...rest }: ClerkProv __signUpFallbackRedirectUrl, __clerkJSUrl, __clerkJSVersion, - __ui, + __prefetchUI, __telemetryDisabled, __telemetryDebug, } = clerkState?.__internal_clerk_state || {}; @@ -91,7 +91,7 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl, clerkJSUrl: __clerkJSUrl, clerkJSVersion: __clerkJSVersion, - ui: __ui, + prefetchUI: __prefetchUI, telemetry: { disabled: __telemetryDisabled, debug: __telemetryDebug, diff --git a/packages/react-router/src/client/types.ts b/packages/react-router/src/client/types.ts index cc379c82f61..bd5e4818bbe 100644 --- a/packages/react-router/src/client/types.ts +++ b/packages/react-router/src/client/types.ts @@ -20,7 +20,7 @@ export type ClerkState = { __clerk_debug: any; __clerkJSUrl: string | undefined; __clerkJSVersion: string | undefined; - __ui: false | { url?: string; version?: string } | undefined; + __prefetchUI: boolean | undefined; __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; }; diff --git a/packages/react-router/src/server/utils.ts b/packages/react-router/src/server/utils.ts index 80a7529c0fd..7657fa20893 100644 --- a/packages/react-router/src/server/utils.ts +++ b/packages/react-router/src/server/utils.ts @@ -95,7 +95,7 @@ export function getResponseClerkState(requestState: RequestStateWithRedirectUrls __clerk_debug: debugRequestState(requestState), __clerkJSUrl: envVars.clerkJsUrl, __clerkJSVersion: envVars.clerkJsVersion, - __ui: envVars.ui, + __prefetchUI: envVars.prefetchUI, __telemetryDisabled: envVars.telemetryDisabled, __telemetryDebug: envVars.telemetryDebug, }); diff --git a/packages/react-router/src/utils/env.ts b/packages/react-router/src/utils/env.ts index 7afc4f36a75..2ca769c2b9d 100644 --- a/packages/react-router/src/utils/env.ts +++ b/packages/react-router/src/utils/env.ts @@ -7,20 +7,8 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { return getEnvVariable(`VITE_${name}`, context) || getEnvVariable(name, context); }; - // Build ui config from env vars - const uiDisabled = getValue('CLERK_UI_DISABLED') === 'true'; - const uiUrl = getValue('CLERK_UI_URL'); - const uiVersion = getValue('CLERK_UI_VERSION'); - - const getUiConfig = (): false | { url?: string; version?: string } | undefined => { - if (uiDisabled) { - return false; - } - if (uiUrl || uiVersion) { - return { url: uiUrl || undefined, version: uiVersion || undefined }; - } - return undefined; - }; + // Build prefetchUI config from env vars + const prefetchUIDisabled = getValue('CLERK_PREFETCH_UI_DISABLED') === 'true'; return { publishableKey: getValue('CLERK_PUBLISHABLE_KEY'), @@ -31,7 +19,7 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL'), clerkJsVersion: getValue('CLERK_JS_VERSION'), - ui: getUiConfig(), + prefetchUI: prefetchUIDisabled ? false : undefined, telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), signInForceRedirectUrl: getValue('CLERK_SIGN_IN_FORCE_REDIRECT_URL'), diff --git a/packages/react/src/internal.ts b/packages/react/src/internal.ts index 59bef920d40..2862d6f5ed1 100644 --- a/packages/react/src/internal.ts +++ b/packages/react/src/internal.ts @@ -9,7 +9,7 @@ export { clerkUiScriptUrl, buildClerkUiScriptAttributes, setClerkJsLoadingErrorPackageName, - shouldLoadClerkUi, + shouldPrefetchClerkUi, } from '@clerk/shared/loadClerkJsScript'; export type { Ui } from '@clerk/ui/internal'; diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 8c79f1d0878..2129b5bfc1b 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -1,6 +1,6 @@ import { inBrowser } from '@clerk/shared/browser'; import { clerkEvents, createClerkEventBus } from '@clerk/shared/clerkEventBus'; -import { loadClerkJsScript, loadClerkUiScript, shouldLoadClerkUi } from '@clerk/shared/loadClerkJsScript'; +import { loadClerkJsScript, loadClerkUiScript, shouldPrefetchClerkUi } from '@clerk/shared/loadClerkJsScript'; import type { __internal_AttemptToEnableEnvironmentSettingParams, __internal_AttemptToEnableEnvironmentSettingResult, @@ -509,7 +509,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } private async getClerkUiEntryChunk(): Promise { - if (!shouldLoadClerkUi(this.options.ui)) { + if (!shouldPrefetchClerkUi(this.options.prefetchUI)) { return undefined; } @@ -517,12 +517,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { return this.options.clerkUiCtor; } - const uiOptions = this.options.ui && this.options.ui !== false ? this.options.ui : {}; - await loadClerkUiScript({ ...this.options, - clerkUiVersion: uiOptions.version, - clerkUiUrl: uiOptions.url, publishableKey: this.#publishableKey, proxyUrl: this.proxyUrl, domain: this.domain, diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index de147bb54cd..5dc7d8eb681 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -11,7 +11,7 @@ import { loadClerkJsScript, loadClerkUiScript, setClerkJsLoadingErrorPackageName, - shouldLoadClerkUi, + shouldPrefetchClerkUi, } from '../loadClerkJsScript'; import { loadScript } from '../loadScript'; import { getMajorVersion } from '../versionSelector'; @@ -465,28 +465,16 @@ describe('buildClerkUiScriptAttributes()', () => { }); }); -describe('shouldLoadClerkUi()', () => { - test('returns true when ui is undefined', () => { - expect(shouldLoadClerkUi(undefined)).toBe(true); +describe('shouldPrefetchClerkUi()', () => { + test('returns true when prefetchUI is undefined', () => { + expect(shouldPrefetchClerkUi(undefined)).toBe(true); }); - test('returns false when ui is false', () => { - expect(shouldLoadClerkUi(false)).toBe(false); + test('returns false when prefetchUI is false', () => { + expect(shouldPrefetchClerkUi(false)).toBe(false); }); - test('returns true when ui has url option', () => { - expect(shouldLoadClerkUi({ url: 'https://custom.com/ui.js' })).toBe(true); - }); - - test('returns true when ui has version option', () => { - expect(shouldLoadClerkUi({ version: '1.0.0' })).toBe(true); - }); - - test('returns true when ui has both url and version options', () => { - expect(shouldLoadClerkUi({ url: 'https://custom.com/ui.js', version: '1.0.0' })).toBe(true); - }); - - test('returns true when ui is an empty object', () => { - expect(shouldLoadClerkUi({})).toBe(true); + test('returns true when prefetchUI is true', () => { + expect(shouldPrefetchClerkUi(true)).toBe(true); }); }); diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 5e52ced7d6f..b983a21518f 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -27,12 +27,12 @@ export type LoadClerkJsScriptOptions = { }; /** - * Determines whether the Clerk UI should be loaded based on the `ui` option. - * @param ui - The ui option from ClerkProvider/options - * @returns `true` if UI should be loaded, `false` if it should be skipped + * Determines whether the Clerk UI should be prefetched based on the `prefetchUI` option. + * @param prefetchUI - The prefetchUI option from ClerkProvider/options + * @returns `true` if UI should be prefetched, `false` if it should be skipped */ -export const shouldLoadClerkUi = (ui: false | { version?: string; url?: string } | undefined): boolean => { - return ui !== false; +export const shouldPrefetchClerkUi = (prefetchUI: boolean | undefined): boolean => { + return prefetchUI !== false; }; export type LoadClerkUiScriptOptions = { diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 68d5a0d0ced..675d5b6b0ba 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2410,12 +2410,11 @@ export type IsomorphicClerkOptions = Without & { */ nonce?: string; /** - * Controls loading of the `@clerk/ui` script. - * - `false` - Skip loading the UI (for custom UIs using Control Components) - * - `{ version?, url? }` - Load UI with specific version or URL - * - `undefined` (default) - Load UI normally + * Controls prefetching of the `@clerk/ui` script. + * - `false` - Skip prefetching the UI (for custom UIs using Control Components) + * - `undefined` (default) - Prefetch UI normally */ - ui?: false | { version?: string; url?: string }; + prefetchUI?: boolean; } & MultiDomainAndOrProxy; export interface LoadedClerk extends Clerk { diff --git a/packages/tanstack-react-start/src/client/types.ts b/packages/tanstack-react-start/src/client/types.ts index 7554cab301e..beccafca94f 100644 --- a/packages/tanstack-react-start/src/client/types.ts +++ b/packages/tanstack-react-start/src/client/types.ts @@ -18,7 +18,7 @@ export type ClerkState = { __clerk_debug: any; __clerkJSUrl: string | undefined; __clerkJSVersion: string | undefined; - __ui: false | { url?: string; version?: string } | undefined; + __prefetchUI: boolean | undefined; __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; }; diff --git a/packages/tanstack-react-start/src/client/utils.ts b/packages/tanstack-react-start/src/client/utils.ts index 2bf93ae4de2..c281a0437b2 100644 --- a/packages/tanstack-react-start/src/client/utils.ts +++ b/packages/tanstack-react-start/src/client/utils.ts @@ -28,7 +28,7 @@ export const pickFromClerkInitState = ( __signUpFallbackRedirectUrl, __keylessClaimUrl, __keylessApiKeysUrl, - __ui, + __prefetchUI, } = clerkInitState || {}; return { @@ -41,7 +41,7 @@ export const pickFromClerkInitState = ( signUpUrl: __signUpUrl, clerkJSUrl: __clerkJSUrl, clerkJSVersion: __clerkJSVersion, - ui: __ui, + prefetchUI: __prefetchUI, telemetry: { disabled: __telemetryDisabled, debug: __telemetryDebug, @@ -67,6 +67,6 @@ export const mergeWithPublicEnvs = (restInitState: any) => { clerkJSUrl: restInitState.clerkJSUrl || envVars.clerkJsUrl, clerkJSVersion: restInitState.clerkJSVersion || envVars.clerkJsVersion, signInForceRedirectUrl: restInitState.signInForceRedirectUrl, - ui: restInitState.ui ?? envVars.ui, + prefetchUI: restInitState.prefetchUI ?? envVars.prefetchUI, }; }; diff --git a/packages/tanstack-react-start/src/server/utils/index.ts b/packages/tanstack-react-start/src/server/utils/index.ts index 5a7961216d3..b1cf507830e 100644 --- a/packages/tanstack-react-start/src/server/utils/index.ts +++ b/packages/tanstack-react-start/src/server/utils/index.ts @@ -16,20 +16,15 @@ export const wrapWithClerkState = (data: any) => { }; /** - * Returns the clerk state object and observability headers to be injected into a context. + * Returns the prefetchUI config from environment variables. * * @internal */ -function getUiConfigFromEnv(): false | { url?: string; version?: string } | undefined { - const uiDisabled = getEnvVariable('CLERK_UI_DISABLED') === 'true'; - if (uiDisabled) { +function getPrefetchUIFromEnv(): boolean | undefined { + const prefetchUIDisabled = getEnvVariable('CLERK_PREFETCH_UI_DISABLED') === 'true'; + if (prefetchUIDisabled) { return false; } - const uiUrl = getEnvVariable('CLERK_UI_URL'); - const uiVersion = getEnvVariable('CLERK_UI_VERSION'); - if (uiUrl || uiVersion) { - return { url: uiUrl || undefined, version: uiVersion || undefined }; - } return undefined; } @@ -49,7 +44,7 @@ export function getResponseClerkState(requestState: RequestState, additionalStat __clerk_debug: debugRequestState(requestState), __clerkJSUrl: getEnvVariable('CLERK_JS') || getEnvVariable('CLERK_JS_URL'), __clerkJSVersion: getEnvVariable('CLERK_JS_VERSION'), - __ui: getUiConfigFromEnv(), + __prefetchUI: getPrefetchUIFromEnv(), __telemetryDisabled: isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')), __telemetryDebug: isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')), __signInForceRedirectUrl: diff --git a/packages/tanstack-react-start/src/utils/env.ts b/packages/tanstack-react-start/src/utils/env.ts index 58610027234..dc24d457bbc 100644 --- a/packages/tanstack-react-start/src/utils/env.ts +++ b/packages/tanstack-react-start/src/utils/env.ts @@ -6,20 +6,8 @@ export const getPublicEnvVariables = () => { return getEnvVariable(`VITE_${name}`) || getEnvVariable(name); }; - // Build ui config from env vars - const uiDisabled = getValue('CLERK_UI_DISABLED') === 'true'; - const uiUrl = getValue('CLERK_UI_URL'); - const uiVersion = getValue('CLERK_UI_VERSION'); - - const getUiConfig = (): false | { url?: string; version?: string } | undefined => { - if (uiDisabled) { - return false; - } - if (uiUrl || uiVersion) { - return { url: uiUrl || undefined, version: uiVersion || undefined }; - } - return undefined; - }; + // Build prefetchUI config from env vars + const prefetchUIDisabled = getValue('CLERK_PREFETCH_UI_DISABLED') === 'true'; return { publishableKey: getValue('CLERK_PUBLISHABLE_KEY'), @@ -30,7 +18,7 @@ export const getPublicEnvVariables = () => { signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'), clerkJsVersion: getValue('CLERK_JS_VERSION'), - ui: getUiConfig(), + prefetchUI: prefetchUIDisabled ? false : undefined, telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), afterSignInUrl: getValue('CLERK_AFTER_SIGN_IN_URL'), diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index 29f237d85e7..78c83882daa 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -4,7 +4,7 @@ import { loadClerkJsScript, type LoadClerkJsScriptOptions, loadClerkUiScript, - shouldLoadClerkUi, + shouldPrefetchClerkUi, } from '@clerk/shared/loadClerkJsScript'; import type { Clerk, @@ -83,17 +83,13 @@ export const clerkPlugin: Plugin<[PluginOptions]> = { void (async () => { try { const clerkPromise = loadClerkJsScript(options); - // Skip UI loading when ui={false} - const clerkUiCtorPromise = !shouldLoadClerkUi(pluginOptions.ui) + // Skip UI loading when prefetchUI={false} + const clerkUiCtorPromise = !shouldPrefetchClerkUi(pluginOptions.prefetchUI) ? Promise.resolve(undefined) : pluginOptions.clerkUiCtor ? Promise.resolve(pluginOptions.clerkUiCtor) : (async () => { - await loadClerkUiScript({ - ...options, - clerkUiUrl: typeof pluginOptions.ui === 'object' ? pluginOptions.ui.url : undefined, - clerkUiVersion: typeof pluginOptions.ui === 'object' ? pluginOptions.ui.version : undefined, - }); + await loadClerkUiScript(options); if (!window.__internal_ClerkUiCtor) { throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); } From 7c3fb9a6f7b5447cb519059b655dbe791bcec1d8 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 13:26:32 -0600 Subject: [PATCH 15/30] fix: update nuxt and tanstack-react-start for prefetchUI rename --- packages/nuxt/src/module.ts | 4 ++-- packages/tanstack-react-start/src/server/constants.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index d0db61a87d2..687968c04e7 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -68,8 +68,8 @@ export default defineNuxtModule({ // NUXT_PUBLIC_CLERK_JS_URL env vars. jsUrl: options.clerkJSUrl, clerkJSVersion: options.clerkJSVersion, - // UI config: can be false, { url?, version? }, or undefined - ui: options.ui, + // prefetchUI config: can be false, { url?, version? }, or undefined + prefetchUI: options.prefetchUI, isSatellite: options.isSatellite, // Backend specific variables that are safe to share. // We want them to be overridable like the other public keys (e.g NUXT_PUBLIC_CLERK_PROXY_URL) diff --git a/packages/tanstack-react-start/src/server/constants.ts b/packages/tanstack-react-start/src/server/constants.ts index 68670c65579..a757a2b9497 100644 --- a/packages/tanstack-react-start/src/server/constants.ts +++ b/packages/tanstack-react-start/src/server/constants.ts @@ -10,7 +10,7 @@ export const commonEnvs = () => { // Public environment variables CLERK_JS_VERSION: publicEnvs.clerkJsVersion, CLERK_JS_URL: publicEnvs.clerkJsUrl, - UI: publicEnvs.ui, + PREFETCH_UI: publicEnvs.prefetchUI, PUBLISHABLE_KEY: publicEnvs.publishableKey, DOMAIN: publicEnvs.domain, PROXY_URL: publicEnvs.proxyUrl, From ff90124924344c0cf9f52211fe4cc7d2d7f8f38b Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 13:29:05 -0600 Subject: [PATCH 16/30] format --- packages/astro/src/integration/create-integration.ts | 1 - packages/clerk-js/package.json | 8 ++++---- packages/clerk-js/rspack.config.js | 11 +---------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/astro/src/integration/create-integration.ts b/packages/astro/src/integration/create-integration.ts index dab761aedf8..ab9f19e8b37 100644 --- a/packages/astro/src/integration/create-integration.ts +++ b/packages/astro/src/integration/create-integration.ts @@ -31,7 +31,6 @@ function createIntegration() logger.error('Missing adapter, please update your Astro config to use one.'); } - const internalParams: ClerkOptions = { ...params, sdkMetadata: { diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 6f5db6c28bf..a41a1197d97 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -21,10 +21,6 @@ }, "license": "MIT", "author": "Clerk", - "main": "dist/clerk.js", - "jsdelivr": "dist/clerk.browser.js", - "module": "dist/clerk.mjs", - "types": "dist/types/index.d.ts", "exports": { ".": { "react-native": { @@ -51,6 +47,10 @@ } } }, + "main": "dist/clerk.js", + "jsdelivr": "dist/clerk.browser.js", + "module": "dist/clerk.mjs", + "types": "dist/types/index.d.ts", "files": [ "dist", "no-rhc" diff --git a/packages/clerk-js/rspack.config.js b/packages/clerk-js/rspack.config.js index 843ceabc177..f010c4ebe46 100644 --- a/packages/clerk-js/rspack.config.js +++ b/packages/clerk-js/rspack.config.js @@ -412,16 +412,7 @@ const prodConfig = ({ mode, env, analysis }) => { return [clerkBrowser]; } - return [ - clerkBrowser, - clerkLegacyBrowser, - clerkNative, - clerkCHIPS, - clerkEsm, - clerkEsmNoRHC, - clerkCjs, - clerkCjsNoRHC, - ]; + return [clerkBrowser, clerkLegacyBrowser, clerkNative, clerkCHIPS, clerkEsm, clerkEsmNoRHC, clerkCjs, clerkCjsNoRHC]; }; /** From a0068a4bee535f64691e3f0920e4930baf1f5b39 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 13:39:12 -0600 Subject: [PATCH 17/30] fix: update ClerkProvider type tests for prefetchUI prop rename --- .../contexts/__tests__/ClerkProvider.test.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx b/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx index 2d3cf823453..1d98d20da67 100644 --- a/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx +++ b/packages/react/src/contexts/__tests__/ClerkProvider.test.tsx @@ -35,28 +35,23 @@ describe('ClerkProvider', () => { }); }); - describe('ui', () => { + describe('prefetchUI', () => { const defaultProps = { publishableKey: 'test', children: '' }; - it('accepts false to disable UI loading', () => { - expectTypeOf({ ...defaultProps, ui: false as const }).toMatchTypeOf(); + it('accepts false to disable UI prefetching', () => { + expectTypeOf({ ...defaultProps, prefetchUI: false as const }).toMatchTypeOf(); }); - it('accepts undefined for default UI loading', () => { - expectTypeOf({ ...defaultProps, ui: undefined }).toMatchTypeOf(); + it('accepts undefined for default behavior', () => { + expectTypeOf({ ...defaultProps, prefetchUI: undefined }).toMatchTypeOf(); }); - it('accepts object with url and version options', () => { - expectTypeOf({ ...defaultProps, ui: { url: 'https://custom.com/ui.js' } }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, ui: { version: '1.0.0' } }).toMatchTypeOf(); - expectTypeOf({ - ...defaultProps, - ui: { url: 'https://custom.com/ui.js', version: '1.0.0' }, - }).toMatchTypeOf(); + it('accepts true to explicitly enable UI prefetching', () => { + expectTypeOf({ ...defaultProps, prefetchUI: true as const }).toMatchTypeOf(); }); - it('accepts empty object', () => { - expectTypeOf({ ...defaultProps, ui: {} }).toMatchTypeOf(); + it('rejects non-boolean values', () => { + expectTypeOf({ ...defaultProps, prefetchUI: 'test' }).not.toMatchTypeOf(); }); }); From 40b99fd5682f8aa84b4dfe75b02f2d00471c9875 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 13:42:17 -0600 Subject: [PATCH 18/30] fix: align env schema variable name with usage (PUBLIC_CLERK_PREFETCH_UI_DISABLED) --- packages/astro/src/integration/create-integration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/integration/create-integration.ts b/packages/astro/src/integration/create-integration.ts index ab9f19e8b37..02c4156942a 100644 --- a/packages/astro/src/integration/create-integration.ts +++ b/packages/astro/src/integration/create-integration.ts @@ -165,7 +165,7 @@ function createClerkEnvSchema() { PUBLIC_CLERK_DOMAIN: envField.string({ context: 'client', access: 'public', optional: true, url: true }), PUBLIC_CLERK_JS_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }), PUBLIC_CLERK_JS_VERSION: envField.string({ context: 'client', access: 'public', optional: true }), - PUBLIC_CLERK_UI_DISABLED: envField.boolean({ context: 'client', access: 'public', optional: true }), + PUBLIC_CLERK_PREFETCH_UI_DISABLED: envField.boolean({ context: 'client', access: 'public', optional: true }), PUBLIC_CLERK_UI_URL: envField.string({ context: 'client', access: 'public', optional: true, url: true }), PUBLIC_CLERK_UI_VERSION: envField.string({ context: 'client', access: 'public', optional: true }), PUBLIC_CLERK_TELEMETRY_DISABLED: envField.boolean({ context: 'client', access: 'public', optional: true }), From e67dfa5fc126ad53dc028bd7281b6328e51a92ad Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 13:51:03 -0600 Subject: [PATCH 19/30] fix: add shouldPrefetchClerkUi to mock in isomorphicClerk test --- packages/react/src/__tests__/isomorphicClerk.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/src/__tests__/isomorphicClerk.test.ts b/packages/react/src/__tests__/isomorphicClerk.test.ts index 3e96f127ef2..a0efe53f51b 100644 --- a/packages/react/src/__tests__/isomorphicClerk.test.ts +++ b/packages/react/src/__tests__/isomorphicClerk.test.ts @@ -7,6 +7,7 @@ import { IsomorphicClerk } from '../isomorphicClerk'; vi.mock('@clerk/shared/loadClerkJsScript', () => ({ loadClerkJsScript: vi.fn().mockResolvedValue(null), loadClerkUiScript: vi.fn().mockResolvedValue(null), + shouldPrefetchClerkUi: vi.fn().mockReturnValue(true), })); describe('isomorphicClerk', () => { From 06ca9e75f1268d5eff80ac5560d8ccdf3ca0b10f Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 20:14:39 -0600 Subject: [PATCH 20/30] chore(clerk-js): remove obsolete headless test --- packages/clerk-js/src/__tests__/headless.test.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 packages/clerk-js/src/__tests__/headless.test.ts diff --git a/packages/clerk-js/src/__tests__/headless.test.ts b/packages/clerk-js/src/__tests__/headless.test.ts deleted file mode 100644 index 8ea5196a2a9..00000000000 --- a/packages/clerk-js/src/__tests__/headless.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @vitest-environment node - */ - -import { describe, expect, it } from 'vitest'; - -describe('clerk/headless', () => { - it('JS-689: should not error when loading headless', () => { - expect(() => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - require('../../headless/index.js'); - }).not.toThrow(); - }); -}); From 4120be2d538b736d7c4540a29d80e689fbe42de8 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 20:16:38 -0600 Subject: [PATCH 21/30] chore(clerk-js): remove headless references from config files --- packages/clerk-js/tsconfig.declarations.json | 2 -- packages/clerk-js/turbo.json | 1 - packages/clerk-js/vitest.config.mts | 2 -- 3 files changed, 5 deletions(-) diff --git a/packages/clerk-js/tsconfig.declarations.json b/packages/clerk-js/tsconfig.declarations.json index 29c5eced402..08f4a4d7a89 100644 --- a/packages/clerk-js/tsconfig.declarations.json +++ b/packages/clerk-js/tsconfig.declarations.json @@ -14,8 +14,6 @@ "include": [ "src/index.ts", "src/index.browser.ts", - "src/index.headless.ts", - "src/index.headless.browser.ts", "src/**/*.d.ts" ] } diff --git a/packages/clerk-js/turbo.json b/packages/clerk-js/turbo.json index f9aa3069acd..d0557a2bc8c 100644 --- a/packages/clerk-js/turbo.json +++ b/packages/clerk-js/turbo.json @@ -4,7 +4,6 @@ "build": { "inputs": [ "*.d.ts", - "headless/**", "src/**", "tsconfig.json", "tsconfig.declarations.json", diff --git a/packages/clerk-js/vitest.config.mts b/packages/clerk-js/vitest.config.mts index c74923a9bfd..e343397351c 100644 --- a/packages/clerk-js/vitest.config.mts +++ b/packages/clerk-js/vitest.config.mts @@ -41,8 +41,6 @@ export default defineConfig({ 'src/**/index.ts', 'src/**/index.browser.ts', 'src/**/index.chips.browser.ts', - 'src/**/index.headless.ts', - 'src/**/index.headless.browser.ts', 'src/**/index.legacy.browser.ts', 'src/**/coverage/**', 'src/**/dist/**', From 54ac8269f757fb429e8c64a3f6ce717c1126f2ba Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 20:25:40 -0600 Subject: [PATCH 22/30] wip --- packages/clerk-js/tsconfig.declarations.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/clerk-js/tsconfig.declarations.json b/packages/clerk-js/tsconfig.declarations.json index 08f4a4d7a89..1ed4e456652 100644 --- a/packages/clerk-js/tsconfig.declarations.json +++ b/packages/clerk-js/tsconfig.declarations.json @@ -11,9 +11,5 @@ "declarationDir": "./dist/types", "noImplicitReturns": false }, - "include": [ - "src/index.ts", - "src/index.browser.ts", - "src/**/*.d.ts" - ] + "include": ["src/index.ts", "src/index.browser.ts", "src/**/*.d.ts"] } From a1416d66c245ac2a76b296ddde40ecc7153837d0 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 20:40:26 -0600 Subject: [PATCH 23/30] fix: restore clerkUiUrl env support for integration tests --- packages/astro/src/internal/merge-env-vars-with-params.ts | 4 +++- packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts | 1 + packages/nuxt/src/module.ts | 7 ++++--- packages/nuxt/src/runtime/plugin.ts | 3 ++- packages/react-router/src/utils/env.ts | 1 + packages/tanstack-react-start/src/utils/env.ts | 1 + 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index 4d813662d85..a94cfe64bda 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -36,9 +36,10 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish telemetry: paramTelemetry, clerkJSUrl: paramClerkJSUrl, clerkJSVersion: paramClerkJSVersion, + clerkUiUrl: paramClerkUiUrl, prefetchUI: paramPrefetchUI, ...rest - } = params || {}; + } = params || {} as any; return { signInUrl: paramSignIn || import.meta.env.PUBLIC_CLERK_SIGN_IN_URL, @@ -49,6 +50,7 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish publishableKey: paramPublishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, clerkJSVersion: paramClerkJSVersion || import.meta.env.PUBLIC_CLERK_JS_VERSION, + clerkUiUrl: paramClerkUiUrl || import.meta.env.PUBLIC_CLERK_UI_URL, prefetchUI: mergePrefetchUIConfig(paramPrefetchUI), telemetry: paramTelemetry || { disabled: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DISABLED), diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index 7863835ddca..ecb947a89f2 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -24,6 +24,7 @@ export const mergeNextClerkPropsWithEnv = (props: Omit({ signUpForceRedirectUrl: options.signUpForceRedirectUrl, signUpUrl: options.signUpUrl, domain: options.domain, - // Using jsUrl instead of clerkJSUrl to support - // NUXT_PUBLIC_CLERK_JS_URL env vars. + // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUiUrl to support + // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. jsUrl: options.clerkJSUrl, + uiUrl: (options as any).clerkUiUrl, clerkJSVersion: options.clerkJSVersion, - // prefetchUI config: can be false, { url?, version? }, or undefined + // prefetchUI config: can be false or undefined prefetchUI: options.prefetchUI, isSatellite: options.isSatellite, // Backend specific variables that are safe to share. diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index 41b743e7078..ec58dec15cc 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -21,8 +21,9 @@ export default defineNuxtPlugin(nuxtApp => { nuxtApp.vueApp.use(clerkPlugin as any, { ...clerkConfig, - // Map jsUrl to clerkJSUrl as expected by the Vue plugin + // Map jsUrl/uiUrl to clerkJSUrl/clerkUiUrl as expected by the Vue plugin clerkJSUrl: clerkConfig.jsUrl, + clerkUiUrl: (clerkConfig as any).uiUrl, sdkMetadata: { name: PACKAGE_NAME, version: PACKAGE_VERSION, diff --git a/packages/react-router/src/utils/env.ts b/packages/react-router/src/utils/env.ts index 2ca769c2b9d..fd905be23e9 100644 --- a/packages/react-router/src/utils/env.ts +++ b/packages/react-router/src/utils/env.ts @@ -19,6 +19,7 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL'), clerkJsVersion: getValue('CLERK_JS_VERSION'), + clerkUiUrl: getValue('CLERK_UI_URL'), prefetchUI: prefetchUIDisabled ? false : undefined, telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), diff --git a/packages/tanstack-react-start/src/utils/env.ts b/packages/tanstack-react-start/src/utils/env.ts index dc24d457bbc..a844d54dffa 100644 --- a/packages/tanstack-react-start/src/utils/env.ts +++ b/packages/tanstack-react-start/src/utils/env.ts @@ -18,6 +18,7 @@ export const getPublicEnvVariables = () => { signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'), clerkJsVersion: getValue('CLERK_JS_VERSION'), + clerkUiUrl: getValue('CLERK_UI_URL'), prefetchUI: prefetchUIDisabled ? false : undefined, telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), From cda44dfabac929373bc77aaf2728bc619bac2d52 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 20:45:42 -0600 Subject: [PATCH 24/30] fix: restore clerkUiUrl in public types --- packages/astro/src/internal/merge-env-vars-with-params.ts | 2 +- packages/astro/src/types.ts | 4 ++++ packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts | 2 +- packages/nuxt/src/global.d.ts | 7 ++++++- packages/nuxt/src/module.ts | 2 +- packages/nuxt/src/runtime/plugin.ts | 2 +- packages/shared/src/types/clerk.ts | 4 ++++ 7 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index a94cfe64bda..f4a9b7debb8 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -39,7 +39,7 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish clerkUiUrl: paramClerkUiUrl, prefetchUI: paramPrefetchUI, ...rest - } = params || {} as any; + } = params || {}; return { signInUrl: paramSignIn || import.meta.env.PUBLIC_CLERK_SIGN_IN_URL, diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 67f8a932aa1..c92c4c8d541 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -31,6 +31,10 @@ type AstroClerkIntegrationParams = Without< appearance?: Appearance; clerkJSUrl?: string; clerkJSVersion?: string; + /** + * The URL that `@clerk/ui` should be hot-loaded from. + */ + clerkUiUrl?: string; /** * Controls prefetching of the `@clerk/ui` script. * - `false` - Skip prefetching the UI (for custom UIs using Control Components) diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index ecb947a89f2..2fc30f98c86 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -24,7 +24,7 @@ export const mergeNextClerkPropsWithEnv = (props: Omit & { + clerk: Omit & { /** * The URL that `@clerk/clerk-js` should be hot-loaded from. * Supports NUXT_PUBLIC_CLERK_JS_URL env var. */ jsUrl?: string; + /** + * The URL that `@clerk/ui` should be hot-loaded from. + * Supports NUXT_PUBLIC_CLERK_UI_URL env var. + */ + uiUrl?: string; }; } } diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 6f4f05a7f9c..a28f1943a38 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -67,7 +67,7 @@ export default defineNuxtModule({ // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUiUrl to support // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. jsUrl: options.clerkJSUrl, - uiUrl: (options as any).clerkUiUrl, + uiUrl: options.clerkUiUrl, clerkJSVersion: options.clerkJSVersion, // prefetchUI config: can be false or undefined prefetchUI: options.prefetchUI, diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index ec58dec15cc..8879860afb6 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -23,7 +23,7 @@ export default defineNuxtPlugin(nuxtApp => { ...clerkConfig, // Map jsUrl/uiUrl to clerkJSUrl/clerkUiUrl as expected by the Vue plugin clerkJSUrl: clerkConfig.jsUrl, - clerkUiUrl: (clerkConfig as any).uiUrl, + clerkUiUrl: clerkConfig.uiUrl, sdkMetadata: { name: PACKAGE_NAME, version: PACKAGE_VERSION, diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 675d5b6b0ba..4c33d8ba18c 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2401,6 +2401,10 @@ export type IsomorphicClerkOptions = Without & { * The npm version for `@clerk/clerk-js`. */ clerkJSVersion?: string; + /** + * The URL that `@clerk/ui` should be hot-loaded from. + */ + clerkUiUrl?: string; /** * The Clerk Publishable Key for your instance. This can be found on the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard. */ From 60e57320a33f9c363dd9eb72145ad7a3397d3718 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 20:49:19 -0600 Subject: [PATCH 25/30] chore: rename headless-variant test to prefetch-ui --- .../tests/{headless-variant.test.ts => prefetch-ui.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename integration/tests/{headless-variant.test.ts => prefetch-ui.test.ts} (100%) diff --git a/integration/tests/headless-variant.test.ts b/integration/tests/prefetch-ui.test.ts similarity index 100% rename from integration/tests/headless-variant.test.ts rename to integration/tests/prefetch-ui.test.ts From 3ef7871d2a7c8c5b17187a2ec22945978a9631ac Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 21:18:16 -0600 Subject: [PATCH 26/30] fix: update nextjs type tests for new prefetchUI API --- .../nextjs/src/pages/__tests__/index.test.tsx | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/nextjs/src/pages/__tests__/index.test.tsx b/packages/nextjs/src/pages/__tests__/index.test.tsx index d3cb3f3db95..c58c383cea8 100644 --- a/packages/nextjs/src/pages/__tests__/index.test.tsx +++ b/packages/nextjs/src/pages/__tests__/index.test.tsx @@ -17,28 +17,27 @@ describe('ClerkProvider', () => { }); }); - describe('ui', () => { + describe('prefetchUI', () => { const defaultProps = { children: '' }; - it('accepts false to disable UI loading', () => { - expectTypeOf({ ...defaultProps, ui: false as const }).toMatchTypeOf(); + it('accepts false to disable UI prefetching', () => { + expectTypeOf({ ...defaultProps, prefetchUI: false as const }).toMatchTypeOf(); }); - it('accepts undefined for default UI loading', () => { - expectTypeOf({ ...defaultProps, ui: undefined }).toMatchTypeOf(); + it('accepts undefined for default UI prefetching', () => { + expectTypeOf({ ...defaultProps, prefetchUI: undefined }).toMatchTypeOf(); }); + }); + + describe('clerkUiUrl', () => { + const defaultProps = { children: '' }; - it('accepts object with url and version options', () => { - expectTypeOf({ ...defaultProps, ui: { url: 'https://custom.com/ui.js' } }).toMatchTypeOf(); - expectTypeOf({ ...defaultProps, ui: { version: '1.0.0' } }).toMatchTypeOf(); - expectTypeOf({ - ...defaultProps, - ui: { url: 'https://custom.com/ui.js', version: '1.0.0' }, - }).toMatchTypeOf(); + it('accepts string URL for custom UI location', () => { + expectTypeOf({ ...defaultProps, clerkUiUrl: 'https://custom.com/ui.js' }).toMatchTypeOf(); }); - it('accepts empty object', () => { - expectTypeOf({ ...defaultProps, ui: {} }).toMatchTypeOf(); + it('accepts undefined', () => { + expectTypeOf({ ...defaultProps, clerkUiUrl: undefined }).toMatchTypeOf(); }); }); From 11e31898c4d90fdc71540abfa12cadc5446423a8 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 21:19:18 -0600 Subject: [PATCH 27/30] fix: pass clerkUiUrl to ClerkScripts opts --- packages/nextjs/src/utils/clerk-script.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/utils/clerk-script.tsx b/packages/nextjs/src/utils/clerk-script.tsx index e649128fafb..2465ca01916 100644 --- a/packages/nextjs/src/utils/clerk-script.tsx +++ b/packages/nextjs/src/utils/clerk-script.tsx @@ -44,7 +44,7 @@ function ClerkScript(props: ClerkScriptProps) { } export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) { - const { publishableKey, clerkJSUrl, clerkJSVersion, nonce, prefetchUI } = useClerkNextOptions(); + const { publishableKey, clerkJSUrl, clerkJSVersion, clerkUiUrl, nonce, prefetchUI } = useClerkNextOptions(); const { domain, proxyUrl } = useClerk(); if (!publishableKey) { @@ -55,6 +55,7 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) publishableKey, clerkJSUrl, clerkJSVersion, + clerkUiUrl, nonce, domain, proxyUrl, From 192a7766d487555d9304a48a48a148ee8ce170d1 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 22:01:09 -0600 Subject: [PATCH 28/30] fix: pass clerkUiUrl through astro and react-router --- packages/astro/src/env.d.ts | 1 + packages/astro/src/server/build-clerk-hotload-script.ts | 1 + packages/astro/src/server/get-safe-env.ts | 1 + packages/react-router/src/client/ReactRouterClerkProvider.tsx | 2 ++ packages/react-router/src/client/types.ts | 1 + packages/react-router/src/server/utils.ts | 1 + 6 files changed, 7 insertions(+) diff --git a/packages/astro/src/env.d.ts b/packages/astro/src/env.d.ts index 605af19942e..68ebfcf200a 100644 --- a/packages/astro/src/env.d.ts +++ b/packages/astro/src/env.d.ts @@ -5,6 +5,7 @@ interface InternalEnv { readonly PUBLIC_CLERK_PUBLISHABLE_KEY?: string; readonly PUBLIC_CLERK_JS_URL?: string; readonly PUBLIC_CLERK_JS_VERSION?: string; + readonly PUBLIC_CLERK_UI_URL?: string; readonly PUBLIC_CLERK_PREFETCH_UI_DISABLED?: string; readonly CLERK_API_KEY?: string; readonly CLERK_API_URL?: string; diff --git a/packages/astro/src/server/build-clerk-hotload-script.ts b/packages/astro/src/server/build-clerk-hotload-script.ts index 37dc2045933..083e199d06f 100644 --- a/packages/astro/src/server/build-clerk-hotload-script.ts +++ b/packages/astro/src/server/build-clerk-hotload-script.ts @@ -35,6 +35,7 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { } const clerkUiScriptSrc = clerkUiScriptUrl({ + clerkUiUrl: env.clerkUiUrl, domain, proxyUrl, publishableKey, diff --git a/packages/astro/src/server/get-safe-env.ts b/packages/astro/src/server/get-safe-env.ts index 7bf9948c02e..976a8d94b24 100644 --- a/packages/astro/src/server/get-safe-env.ts +++ b/packages/astro/src/server/get-safe-env.ts @@ -34,6 +34,7 @@ function getSafeEnv(context: ContextOrLocals) { signUpUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_UP_URL', context), clerkJsUrl: getContextEnvVar('PUBLIC_CLERK_JS_URL', context), clerkJsVersion: getContextEnvVar('PUBLIC_CLERK_JS_VERSION', context), + clerkUiUrl: getContextEnvVar('PUBLIC_CLERK_UI_URL', context), prefetchUI: prefetchUIDisabled ? false : undefined, apiVersion: getContextEnvVar('CLERK_API_VERSION', context), apiUrl: getContextEnvVar('CLERK_API_URL', context), diff --git a/packages/react-router/src/client/ReactRouterClerkProvider.tsx b/packages/react-router/src/client/ReactRouterClerkProvider.tsx index c47d3175451..d2d2548588a 100644 --- a/packages/react-router/src/client/ReactRouterClerkProvider.tsx +++ b/packages/react-router/src/client/ReactRouterClerkProvider.tsx @@ -63,6 +63,7 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv __signUpFallbackRedirectUrl, __clerkJSUrl, __clerkJSVersion, + __clerkUiUrl, __prefetchUI, __telemetryDisabled, __telemetryDebug, @@ -91,6 +92,7 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl, clerkJSUrl: __clerkJSUrl, clerkJSVersion: __clerkJSVersion, + clerkUiUrl: __clerkUiUrl, prefetchUI: __prefetchUI, telemetry: { disabled: __telemetryDisabled, diff --git a/packages/react-router/src/client/types.ts b/packages/react-router/src/client/types.ts index bd5e4818bbe..bb6311ddc18 100644 --- a/packages/react-router/src/client/types.ts +++ b/packages/react-router/src/client/types.ts @@ -20,6 +20,7 @@ export type ClerkState = { __clerk_debug: any; __clerkJSUrl: string | undefined; __clerkJSVersion: string | undefined; + __clerkUiUrl: string | undefined; __prefetchUI: boolean | undefined; __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; diff --git a/packages/react-router/src/server/utils.ts b/packages/react-router/src/server/utils.ts index 7657fa20893..a65ed6361cb 100644 --- a/packages/react-router/src/server/utils.ts +++ b/packages/react-router/src/server/utils.ts @@ -95,6 +95,7 @@ export function getResponseClerkState(requestState: RequestStateWithRedirectUrls __clerk_debug: debugRequestState(requestState), __clerkJSUrl: envVars.clerkJsUrl, __clerkJSVersion: envVars.clerkJsVersion, + __clerkUiUrl: envVars.clerkUiUrl, __prefetchUI: envVars.prefetchUI, __telemetryDisabled: envVars.telemetryDisabled, __telemetryDebug: envVars.telemetryDebug, From 16453c111cc83e2f0ab89435e0dbac59030187a9 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 22:06:06 -0600 Subject: [PATCH 29/30] fix: rename clerkUiUrl to clerkUIUrl for consistency with clerkJSUrl --- packages/astro/src/internal/merge-env-vars-with-params.ts | 4 ++-- packages/astro/src/server/build-clerk-hotload-script.ts | 2 +- packages/astro/src/server/get-safe-env.ts | 2 +- packages/astro/src/types.ts | 2 +- packages/nextjs/src/pages/__tests__/index.test.tsx | 6 +++--- packages/nextjs/src/utils/clerk-script.tsx | 4 ++-- packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts | 2 +- packages/nuxt/src/global.d.ts | 2 +- packages/nuxt/src/module.ts | 4 ++-- packages/nuxt/src/runtime/plugin.ts | 4 ++-- .../react-router/src/client/ReactRouterClerkProvider.tsx | 4 ++-- packages/react-router/src/client/types.ts | 2 +- packages/react-router/src/server/utils.ts | 2 +- packages/react-router/src/utils/env.ts | 2 +- packages/shared/src/__tests__/loadClerkJsScript.spec.ts | 4 ++-- packages/shared/src/loadClerkJsScript.ts | 8 ++++---- packages/shared/src/types/clerk.ts | 2 +- packages/tanstack-react-start/src/utils/env.ts | 2 +- 18 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index f4a9b7debb8..a18f0cbc804 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -36,7 +36,7 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish telemetry: paramTelemetry, clerkJSUrl: paramClerkJSUrl, clerkJSVersion: paramClerkJSVersion, - clerkUiUrl: paramClerkUiUrl, + clerkUIUrl: paramClerkUiUrl, prefetchUI: paramPrefetchUI, ...rest } = params || {}; @@ -50,7 +50,7 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & { publish publishableKey: paramPublishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, clerkJSVersion: paramClerkJSVersion || import.meta.env.PUBLIC_CLERK_JS_VERSION, - clerkUiUrl: paramClerkUiUrl || import.meta.env.PUBLIC_CLERK_UI_URL, + clerkUIUrl: paramClerkUiUrl || import.meta.env.PUBLIC_CLERK_UI_URL, prefetchUI: mergePrefetchUIConfig(paramPrefetchUI), telemetry: paramTelemetry || { disabled: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DISABLED), diff --git a/packages/astro/src/server/build-clerk-hotload-script.ts b/packages/astro/src/server/build-clerk-hotload-script.ts index 083e199d06f..0217bbf11f7 100644 --- a/packages/astro/src/server/build-clerk-hotload-script.ts +++ b/packages/astro/src/server/build-clerk-hotload-script.ts @@ -35,7 +35,7 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { } const clerkUiScriptSrc = clerkUiScriptUrl({ - clerkUiUrl: env.clerkUiUrl, + clerkUIUrl: env.clerkUIUrl, domain, proxyUrl, publishableKey, diff --git a/packages/astro/src/server/get-safe-env.ts b/packages/astro/src/server/get-safe-env.ts index 976a8d94b24..e45d48848b9 100644 --- a/packages/astro/src/server/get-safe-env.ts +++ b/packages/astro/src/server/get-safe-env.ts @@ -34,7 +34,7 @@ function getSafeEnv(context: ContextOrLocals) { signUpUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_UP_URL', context), clerkJsUrl: getContextEnvVar('PUBLIC_CLERK_JS_URL', context), clerkJsVersion: getContextEnvVar('PUBLIC_CLERK_JS_VERSION', context), - clerkUiUrl: getContextEnvVar('PUBLIC_CLERK_UI_URL', context), + clerkUIUrl: getContextEnvVar('PUBLIC_CLERK_UI_URL', context), prefetchUI: prefetchUIDisabled ? false : undefined, apiVersion: getContextEnvVar('CLERK_API_VERSION', context), apiUrl: getContextEnvVar('CLERK_API_URL', context), diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index c92c4c8d541..523e3583d5d 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -34,7 +34,7 @@ type AstroClerkIntegrationParams = Without< /** * The URL that `@clerk/ui` should be hot-loaded from. */ - clerkUiUrl?: string; + clerkUIUrl?: string; /** * Controls prefetching of the `@clerk/ui` script. * - `false` - Skip prefetching the UI (for custom UIs using Control Components) diff --git a/packages/nextjs/src/pages/__tests__/index.test.tsx b/packages/nextjs/src/pages/__tests__/index.test.tsx index c58c383cea8..b8bf97ccf76 100644 --- a/packages/nextjs/src/pages/__tests__/index.test.tsx +++ b/packages/nextjs/src/pages/__tests__/index.test.tsx @@ -29,15 +29,15 @@ describe('ClerkProvider', () => { }); }); - describe('clerkUiUrl', () => { + describe('clerkUIUrl', () => { const defaultProps = { children: '' }; it('accepts string URL for custom UI location', () => { - expectTypeOf({ ...defaultProps, clerkUiUrl: 'https://custom.com/ui.js' }).toMatchTypeOf(); + expectTypeOf({ ...defaultProps, clerkUIUrl: 'https://custom.com/ui.js' }).toMatchTypeOf(); }); it('accepts undefined', () => { - expectTypeOf({ ...defaultProps, clerkUiUrl: undefined }).toMatchTypeOf(); + expectTypeOf({ ...defaultProps, clerkUIUrl: undefined }).toMatchTypeOf(); }); }); diff --git a/packages/nextjs/src/utils/clerk-script.tsx b/packages/nextjs/src/utils/clerk-script.tsx index 2465ca01916..9ff8b483a93 100644 --- a/packages/nextjs/src/utils/clerk-script.tsx +++ b/packages/nextjs/src/utils/clerk-script.tsx @@ -44,7 +44,7 @@ function ClerkScript(props: ClerkScriptProps) { } export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) { - const { publishableKey, clerkJSUrl, clerkJSVersion, clerkUiUrl, nonce, prefetchUI } = useClerkNextOptions(); + const { publishableKey, clerkJSUrl, clerkJSVersion, clerkUIUrl, nonce, prefetchUI } = useClerkNextOptions(); const { domain, proxyUrl } = useClerk(); if (!publishableKey) { @@ -55,7 +55,7 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) publishableKey, clerkJSUrl, clerkJSVersion, - clerkUiUrl, + clerkUIUrl, nonce, domain, proxyUrl, diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index 2fc30f98c86..f9cf73e45e7 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -24,7 +24,7 @@ export const mergeNextClerkPropsWithEnv = (props: Omit & { + clerk: Omit & { /** * The URL that `@clerk/clerk-js` should be hot-loaded from. * Supports NUXT_PUBLIC_CLERK_JS_URL env var. diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index a28f1943a38..e38f8d864f8 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -64,10 +64,10 @@ export default defineNuxtModule({ signUpForceRedirectUrl: options.signUpForceRedirectUrl, signUpUrl: options.signUpUrl, domain: options.domain, - // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUiUrl to support + // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUIUrl to support // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. jsUrl: options.clerkJSUrl, - uiUrl: options.clerkUiUrl, + uiUrl: options.clerkUIUrl, clerkJSVersion: options.clerkJSVersion, // prefetchUI config: can be false or undefined prefetchUI: options.prefetchUI, diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index 8879860afb6..1e8fd4e89d7 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -21,9 +21,9 @@ export default defineNuxtPlugin(nuxtApp => { nuxtApp.vueApp.use(clerkPlugin as any, { ...clerkConfig, - // Map jsUrl/uiUrl to clerkJSUrl/clerkUiUrl as expected by the Vue plugin + // Map jsUrl/uiUrl to clerkJSUrl/clerkUIUrl as expected by the Vue plugin clerkJSUrl: clerkConfig.jsUrl, - clerkUiUrl: clerkConfig.uiUrl, + clerkUIUrl: clerkConfig.uiUrl, sdkMetadata: { name: PACKAGE_NAME, version: PACKAGE_VERSION, diff --git a/packages/react-router/src/client/ReactRouterClerkProvider.tsx b/packages/react-router/src/client/ReactRouterClerkProvider.tsx index d2d2548588a..990d73f866d 100644 --- a/packages/react-router/src/client/ReactRouterClerkProvider.tsx +++ b/packages/react-router/src/client/ReactRouterClerkProvider.tsx @@ -63,7 +63,7 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv __signUpFallbackRedirectUrl, __clerkJSUrl, __clerkJSVersion, - __clerkUiUrl, + __clerkUIUrl, __prefetchUI, __telemetryDisabled, __telemetryDebug, @@ -92,7 +92,7 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl, clerkJSUrl: __clerkJSUrl, clerkJSVersion: __clerkJSVersion, - clerkUiUrl: __clerkUiUrl, + clerkUIUrl: __clerkUIUrl, prefetchUI: __prefetchUI, telemetry: { disabled: __telemetryDisabled, diff --git a/packages/react-router/src/client/types.ts b/packages/react-router/src/client/types.ts index bb6311ddc18..1c7c15fcbb3 100644 --- a/packages/react-router/src/client/types.ts +++ b/packages/react-router/src/client/types.ts @@ -20,7 +20,7 @@ export type ClerkState = { __clerk_debug: any; __clerkJSUrl: string | undefined; __clerkJSVersion: string | undefined; - __clerkUiUrl: string | undefined; + __clerkUIUrl: string | undefined; __prefetchUI: boolean | undefined; __telemetryDisabled: boolean | undefined; __telemetryDebug: boolean | undefined; diff --git a/packages/react-router/src/server/utils.ts b/packages/react-router/src/server/utils.ts index a65ed6361cb..2070bdeef5e 100644 --- a/packages/react-router/src/server/utils.ts +++ b/packages/react-router/src/server/utils.ts @@ -95,7 +95,7 @@ export function getResponseClerkState(requestState: RequestStateWithRedirectUrls __clerk_debug: debugRequestState(requestState), __clerkJSUrl: envVars.clerkJsUrl, __clerkJSVersion: envVars.clerkJsVersion, - __clerkUiUrl: envVars.clerkUiUrl, + __clerkUIUrl: envVars.clerkUIUrl, __prefetchUI: envVars.prefetchUI, __telemetryDisabled: envVars.telemetryDisabled, __telemetryDebug: envVars.telemetryDebug, diff --git a/packages/react-router/src/utils/env.ts b/packages/react-router/src/utils/env.ts index fd905be23e9..1184161e775 100644 --- a/packages/react-router/src/utils/env.ts +++ b/packages/react-router/src/utils/env.ts @@ -19,7 +19,7 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL'), clerkJsVersion: getValue('CLERK_JS_VERSION'), - clerkUiUrl: getValue('CLERK_UI_URL'), + clerkUIUrl: getValue('CLERK_UI_URL'), prefetchUI: prefetchUIDisabled ? false : undefined, telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index 5dc7d8eb681..16c2819f217 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -387,9 +387,9 @@ describe('clerkUiScriptUrl()', () => { const mockDevPublishableKey = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; const mockProdPublishableKey = 'pk_live_ZXhhbXBsZS5jbGVyay5jb20k'; // example.clerk.com - test('returns clerkUiUrl when provided', () => { + test('returns clerkUIUrl when provided', () => { const customUrl = 'https://custom.clerk.com/ui.js'; - const result = clerkUiScriptUrl({ clerkUiUrl: customUrl, publishableKey: mockDevPublishableKey }); + const result = clerkUiScriptUrl({ clerkUIUrl: customUrl, publishableKey: mockDevPublishableKey }); expect(result).toBe(customUrl); }); diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index b983a21518f..4f8a464753d 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -37,7 +37,7 @@ export const shouldPrefetchClerkUi = (prefetchUI: boolean | undefined): boolean export type LoadClerkUiScriptOptions = { publishableKey: string; - clerkUiUrl?: string; + clerkUIUrl?: string; clerkUiVersion?: string; proxyUrl?: string; domain?: string; @@ -236,10 +236,10 @@ export const clerkJsScriptUrl = (opts: LoadClerkJsScriptOptions) => { }; export const clerkUiScriptUrl = (opts: LoadClerkUiScriptOptions) => { - const { clerkUiUrl, clerkUiVersion, proxyUrl, domain, publishableKey } = opts; + const { clerkUIUrl, clerkUiVersion, proxyUrl, domain, publishableKey } = opts; - if (clerkUiUrl) { - return clerkUiUrl; + if (clerkUIUrl) { + return clerkUIUrl; } const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 4c33d8ba18c..a8ba952e3ab 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2404,7 +2404,7 @@ export type IsomorphicClerkOptions = Without & { /** * The URL that `@clerk/ui` should be hot-loaded from. */ - clerkUiUrl?: string; + clerkUIUrl?: string; /** * The Clerk Publishable Key for your instance. This can be found on the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard. */ diff --git a/packages/tanstack-react-start/src/utils/env.ts b/packages/tanstack-react-start/src/utils/env.ts index a844d54dffa..7af2041b74e 100644 --- a/packages/tanstack-react-start/src/utils/env.ts +++ b/packages/tanstack-react-start/src/utils/env.ts @@ -18,7 +18,7 @@ export const getPublicEnvVariables = () => { signUpUrl: getValue('CLERK_SIGN_UP_URL'), clerkJsUrl: getValue('CLERK_JS_URL') || getValue('CLERK_JS'), clerkJsVersion: getValue('CLERK_JS_VERSION'), - clerkUiUrl: getValue('CLERK_UI_URL'), + clerkUIUrl: getValue('CLERK_UI_URL'), prefetchUI: prefetchUIDisabled ? false : undefined, telemetryDisabled: isTruthy(getValue('CLERK_TELEMETRY_DISABLED')), telemetryDebug: isTruthy(getValue('CLERK_TELEMETRY_DEBUG')), From 1719aea91ff6a8d2eed3c61d5af09da4d670da55 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 21 Jan 2026 22:18:33 -0600 Subject: [PATCH 30/30] fix: rename clerkUiUrl to clerkUIUrl in integration templates --- integration/templates/custom-flows-react-vite/src/main.tsx | 2 +- integration/templates/expo-web/app/_layout.tsx | 2 +- integration/templates/react-cra/src/index.tsx | 2 +- integration/templates/react-router-library/src/main.tsx | 2 +- integration/templates/react-router-node/app/root.tsx | 2 +- integration/templates/react-vite/src/main.tsx | 2 +- .../templates/tanstack-react-start/src/routes/__root.tsx | 2 +- integration/templates/vue-vite/src/main.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/integration/templates/custom-flows-react-vite/src/main.tsx b/integration/templates/custom-flows-react-vite/src/main.tsx index 7f8b6058c2c..3e718971953 100644 --- a/integration/templates/custom-flows-react-vite/src/main.tsx +++ b/integration/templates/custom-flows-react-vite/src/main.tsx @@ -23,7 +23,7 @@ createRoot(document.getElementById('root')!).render( router.push(to)} routerReplace={to => router.replace(to)} clerkJSUrl={process.env.EXPO_PUBLIC_CLERK_JS_URL} - clerkUiUrl={process.env.EXPO_PUBLIC_CLERK_UI_URL} + clerkUIUrl={process.env.EXPO_PUBLIC_CLERK_UI_URL} appearance={{ options: { showOptionalFields: true, diff --git a/integration/templates/react-cra/src/index.tsx b/integration/templates/react-cra/src/index.tsx index 65271a25b7e..81039d51a20 100644 --- a/integration/templates/react-cra/src/index.tsx +++ b/integration/templates/react-cra/src/index.tsx @@ -10,7 +10,7 @@ root.render( { // @ts-ignore publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string} clerkJSUrl={import.meta.env.VITE_CLERK_JS_URL as string} - clerkUiUrl={import.meta.env.VITE_CLERK_UI_URL as string} + clerkUIUrl={import.meta.env.VITE_CLERK_UI_URL as string} routerPush={(to: string) => navigate(to)} routerReplace={(to: string) => navigate(to, { replace: true })} appearance={{ diff --git a/integration/templates/tanstack-react-start/src/routes/__root.tsx b/integration/templates/tanstack-react-start/src/routes/__root.tsx index 4dd7cf9d763..b9adc012c75 100644 --- a/integration/templates/tanstack-react-start/src/routes/__root.tsx +++ b/integration/templates/tanstack-react-start/src/routes/__root.tsx @@ -29,7 +29,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {