Skip to content
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c5ff0d1
feat(react,vue,astro): Add ui prop for version metadata
jacekradko Jan 23, 2026
d8370e0
chore: add changeset for ui prop
jacekradko Jan 23, 2026
67fdc89
refactor: use ui prop instead of clerkUiCtor in chrome-extension
jacekradko Jan 23, 2026
2399eff
refactor: remove clerkUiCtor from public API, add __internal_forceBun…
jacekradko Jan 23, 2026
89bb1ff
chore: update changeset description
jacekradko Jan 23, 2026
ccac639
refactor: rename clerkUiCtor to ClerkUI and ClerkUiConstructor to Cle…
jacekradko Jan 23, 2026
cf2a354
refactor: rename __internal_ClerkUiCtor to __internal_ClerkUICtor
jacekradko Jan 23, 2026
1d2c8cf
chore: simplify changeset
jacekradko Jan 24, 2026
8f705a8
refactor: rename ui.ctor to ui.ClerkUI
jacekradko Jan 24, 2026
478a0b3
Merge branch 'main' into jrad/ui-prop-cleanup
jacekradko Jan 24, 2026
9517d40
refactor: move ClerkUI inside ui object in ClerkOptions
jacekradko Jan 24, 2026
890cd43
fix: rename ctor to ClerkUI in ui export
jacekradko Jan 24, 2026
7f7665f
chore: fix formatting
jacekradko Jan 24, 2026
a3014de
chore: fix es-ES.ts formatting
jacekradko Jan 24, 2026
9774008
fix(astro): Remove unnecessary eslint-disable directive
jacekradko Jan 24, 2026
e81f20a
test(vue): add unit tests for CDN UI loading with version pinning
jacekradko Jan 24, 2026
d3c96f3
fix(astro): honor bundled UI constructor in getClerkUiEntryChunk
jacekradko Jan 24, 2026
aa0c84b
fix(astro): preserve clerkUiUrl fallback in loadClerkUiScript call
jacekradko Jan 24, 2026
64ff3dc
fix: standardize casing for clerkUIUrl and clerkUIVersion properties
jacekradko Jan 24, 2026
76d7c96
fix(vue): preserve clerkUIUrl fallback when ui.url is not set
jacekradko Jan 24, 2026
c0d3b6f
fix(vue): use type cast for clerkUIUrl/clerkUIVersion fallback
jacekradko Jan 24, 2026
61655b1
fix: update integration templates to use clerkUIUrl casing
jacekradko Jan 24, 2026
978a0d8
Merge branch 'main' into jrad/ui-prop-cleanup
jacekradko Jan 26, 2026
18a45b8
test(react-router): Add ClerkProvider clerkUIUrl prop tests
jacekradko Jan 26, 2026
a9f4cca
fix(react): Use bundled ClerkUI by default, add __internal_preferCDN …
jacekradko Jan 26, 2026
08064ce
fix(react): Await getClerkUiEntryChunk to ensure ClerkUI is resolved
jacekradko Jan 26, 2026
f44ff91
Merge branch 'main' into jrad/ui-prop-cleanup
jacekradko Jan 27, 2026
aaecdf7
fix(integration): Wait for Clerk to load before signOut in component …
jacekradko Jan 27, 2026
b9a490f
test(react): Add unit tests for bundled vs CDN UI loading
jacekradko Jan 27, 2026
21895c2
Merge main into jrad/ui-prop-cleanup
jacekradko Jan 30, 2026
68a17c9
fix(vue): Handle ui prop in clerkPlugin and fix test mocks
jacekradko Jan 30, 2026
35eaeab
feat(react): Support bundled UI via ui.ClerkUI prop
jacekradko Feb 3, 2026
a77ede7
refactor(react,vue): Remove version/url from ui prop, require ClerkUI
jacekradko Feb 4, 2026
4e339ca
fix(vue): Use clerkUICtor option instead of ui.ClerkUI for clerk.load()
jacekradko Feb 4, 2026
f9c063b
Merge branch 'main' into jrad/ui-prop-cleanup
jacekradko Feb 4, 2026
6c4d6fe
feat(ui,react): Add react-server conditional export for RSC support
jacekradko Feb 5, 2026
512942c
test(e2e): Add integration test for bundled UI with react-server cond…
jacekradko Feb 5, 2026
208a1ac
test(ui): add snapshot tests for ui export signature
jacekradko Feb 5, 2026
a7cd2f8
feat(clerk-js,react): Add js prop for bundled clerk-js support
jacekradko Feb 5, 2026
04b6512
Merge branch 'main' into jrad/ui-prop-cleanup
jacekradko Feb 5, 2026
1f13b1a
Merge branch 'jrad/ui-prop-cleanup' into jrad/js-prop-bundling
jacekradko Feb 5, 2026
6a161e0
fix(nextjs): skip UI CDN preload when ui prop is passed
jacekradko Feb 5, 2026
d5234f7
fix(chrome-extension): use ui prop instead of clerkUICtor
jacekradko Feb 5, 2026
a87e669
Merge branch 'main' into jrad/ui-prop-cleanup
jacekradko Feb 6, 2026
562c91e
Merge branch 'jrad/ui-prop-cleanup' into jrad/js-prop-bundling
jacekradko Feb 6, 2026
14df9f0
Merge branch 'main' into jrad/js-prop-bundling
jacekradko Feb 9, 2026
2ff841c
merge origin/main into jrad/js-prop-bundling
jacekradko Feb 9, 2026
fe2ebca
feat(clerk-js): Add js prop for bundling clerk-js with applications
jacekradko Feb 9, 2026
6ccb7b8
chore: fix formatting after merge
jacekradko Feb 9, 2026
ccc0463
fix(clerk-js): fix attw lint for new ESM exports
jacekradko Feb 9, 2026
e5c32e2
fix(astro): replace non-null assertion with optional chain
jacekradko Feb 9, 2026
cf5fe05
fix(clerk-js): fix import sort order in bundled.ts
jacekradko Feb 9, 2026
9807dfc
Merge branch 'main' into jrad/js-prop-bundling
jacekradko Feb 9, 2026
40e2a7c
fix(vue): fix import sort and non-null assertion in plugin.ts
jacekradko Feb 9, 2026
210cac5
fix(integration): remove @clerk/ui from shared test template
jacekradko Feb 9, 2026
968e039
Merge branch 'main' into jrad/js-prop-bundling
jacekradko Feb 9, 2026
7f4710f
fix(react): fix import sort order in types.ts
jacekradko Feb 9, 2026
31f9c2e
Merge branch 'main' into jrad/js-prop-bundling
jacekradko Feb 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .changeset/shiny-owls-dance.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
'@clerk/clerk-js': minor
'@clerk/ui': minor
'@clerk/react': minor
'@clerk/nextjs': minor
Expand All @@ -8,4 +9,4 @@
'@clerk/shared': minor
---

Add `ui` prop to `ClerkProvider` for passing `@clerk/ui`
Add `ui` and `js` props to `ClerkProvider` for passing `@clerk/ui` and `@clerk/clerk-js`
3 changes: 2 additions & 1 deletion integration/presets/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const appRouter = applicationConfig()
.addDependency('react', constants.E2E_REACT_VERSION)
.addDependency('react-dom', constants.E2E_REACT_DOM_VERSION)
.addDependency('@clerk/nextjs', constants.E2E_CLERK_JS_VERSION || linkPackage('nextjs'))
.addDependency('@clerk/shared', linkPackage('shared'));
.addDependency('@clerk/shared', linkPackage('shared'))
.addDependency('@clerk/ui', linkPackage('ui'));

const appRouterTurbo = appRouter.clone().setName('next-app-router-turbopack').addScript('dev', 'pnpm dev');

Expand Down
2 changes: 2 additions & 0 deletions integration/templates/next-app-router/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './globals.css';
import { Inter } from 'next/font/google';
import { ClerkProvider } from '@clerk/nextjs';
import { ui } from '@clerk/ui';

const inter = Inter({ subsets: ['latin'] });

Expand All @@ -12,6 +13,7 @@ export const metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider
ui={ui}
prefetchUI={process.env.NEXT_PUBLIC_CLERK_PREFETCH_UI === 'false' ? false : undefined}
appearance={{
options: {
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface InternalEnv {
readonly PUBLIC_CLERK_UI_URL?: string;
readonly PUBLIC_CLERK_UI_VERSION?: string;
readonly PUBLIC_CLERK_PREFETCH_UI?: string;
readonly PUBLIC_CLERK_SKIP_JS_CDN?: string;
readonly CLERK_API_KEY?: string;
readonly CLERK_API_URL?: string;
readonly CLERK_API_VERSION?: string;
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/integration/create-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function createIntegration<Params extends HotloadAstroClerkIntegrationParams>()
const clerkUIVersion = (params as any)?.clerkUIVersion as string | undefined;
const prefetchUI = (params as any)?.prefetchUI as boolean | undefined;
const hasUI = !!(params as any)?.ui;
const hasJS = !!(params as any)?.js;

return {
name: '@clerk/astro/integration',
Expand Down Expand Up @@ -64,6 +65,7 @@ function createIntegration<Params extends HotloadAstroClerkIntegrationParams>()
prefetchUI === false || hasUI ? 'false' : undefined,
'PUBLIC_CLERK_PREFETCH_UI',
),
...buildEnvVarFromOption(hasJS ? 'true' : undefined, 'PUBLIC_CLERK_SKIP_JS_CDN'),
},

ssr: {
Expand Down Expand Up @@ -173,6 +175,7 @@ function createClerkEnvSchema() {
PUBLIC_CLERK_JS_VERSION: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_UI_VERSION: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_PREFETCH_UI: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_SKIP_JS_CDN: envField.string({ context: 'client', access: 'public', optional: true }),
PUBLIC_CLERK_UI_URL: envField.string({ context: 'client', access: 'public', optional: true, url: 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 }),
Expand Down
11 changes: 10 additions & 1 deletion packages/astro/src/internal/create-clerk-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
loadClerkUIScript,
setClerkJSLoadingErrorPackageName,
} from '@clerk/shared/loadClerkJsScript';
import type { ClerkOptions } from '@clerk/shared/types';
import type { BrowserClerkConstructor, ClerkOptions } from '@clerk/shared/types';
import type { ClerkUIConstructor } from '@clerk/shared/ui';
import type { Ui } from '@clerk/ui/internal';

Expand Down Expand Up @@ -102,9 +102,18 @@ function updateClerkOptions<TUi extends Ui = Ui>(options: AstroClerkUpdateOption

/**
* Loads clerk-js script if not already loaded.
* Uses bundled ClerkJS constructor when js.ClerkJS is present.
* Returns early if window.Clerk already exists.
*/
async function getClerkJsEntryChunk<TUi extends Ui = Ui>(options?: AstroClerkCreateInstanceParams<TUi>): Promise<void> {
const jsProp = options as { js?: { ClerkJS?: BrowserClerkConstructor } } | undefined;
if (jsProp?.js?.ClerkJS) {
window.Clerk = new jsProp.js.ClerkJS(options!.publishableKey, {
proxyUrl: options?.proxyUrl as string,
domain: options?.domain as string,
}) as any;
return;
}
await loadClerkJSScript(options);
}

Expand Down
24 changes: 24 additions & 0 deletions packages/astro/src/server/build-clerk-hotload-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,30 @@ function buildClerkHotloadScript(locals: APIContext['locals']) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const domain = env.domain!;

// Skip ClerkJS CDN script when js prop is bundled
if (env.skipJsCdn) {
if (env.prefetchUI === false) {
return '\n';
}

const clerkUIScriptSrc = clerkUIScriptUrl({
clerkUIUrl: env.clerkUIUrl,
clerkUIVersion: env.clerkUIVersion,
domain,
proxyUrl,
publishableKey,
});

const clerkUIPreload = `
<link rel="preload"
href="${clerkUIScriptSrc}"
as="script"
crossOrigin="anonymous"
/>`;

return clerkUIPreload + '\n';
}

const clerkJsScriptSrc = clerkJSScriptUrl({
clerkJSUrl: env.clerkJsUrl,
clerkJSVersion: env.clerkJsVersion,
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/server/get-safe-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function getSafeEnv(context: ContextOrLocals) {
clerkUIUrl: getContextEnvVar('PUBLIC_CLERK_UI_URL', context),
clerkUIVersion: getContextEnvVar('PUBLIC_CLERK_UI_VERSION', context),
prefetchUI: getContextEnvVar('PUBLIC_CLERK_PREFETCH_UI', context) === 'false' ? false : undefined,
skipJsCdn: getContextEnvVar('PUBLIC_CLERK_SKIP_JS_CDN', context) === 'true',
apiVersion: getContextEnvVar('CLERK_API_VERSION', context),
apiUrl: getContextEnvVar('CLERK_API_URL', context),
telemetryDisabled: isTruthy(getContextEnvVar('PUBLIC_CLERK_TELEMETRY_DISABLED', context)),
Expand Down
24 changes: 23 additions & 1 deletion packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@
"types": "./dist/types/index.d.ts",
"default": "./dist/clerk.no-rhc.js"
}
},
"./bundled": {
"react-server": {
"types": "./dist/esm/server.d.mts",
"import": "./dist/esm/server.mjs",
"default": "./dist/esm/server.mjs"
},
"types": "./dist/esm/bundled.d.mts",
"import": "./dist/esm/bundled.mjs",
"default": "./dist/esm/bundled.mjs"
},
"./entry": {
"types": "./dist/esm/entry.d.mts",
"import": "./dist/esm/entry.mjs",
"default": "./dist/esm/entry.mjs"
},
"./internal": {
"types": "./dist/types/internal/index.d.ts",
"import": "./dist/esm/internal/index.mjs",
"default": "./dist/esm/internal/index.mjs"
}
},
"main": "dist/clerk.js",
Expand All @@ -56,9 +76,10 @@
"no-rhc"
],
"scripts": {
"build": "pnpm build:bundle && pnpm build:declarations",
"build": "pnpm build:bundle && pnpm build:esm && pnpm build:declarations",
"build:analyze": "rspack build --config rspack.config.js --env production --env variant=\"clerk.browser\" --env analysis --analyze",
"build:bundle": "pnpm clean && rspack build --config rspack.config.js --env production",
"build:esm": "tsdown",
"build:declarations": "tsc -p tsconfig.declarations.json",
"build:sandbox": "pnpm --filter @clerk/ui build:umd && rspack build --config rspack.config.js --env production --env sandbox",
"build:stats": "rspack build --config rspack.config.js --env production --json=stats.json --env variant=\"clerk.browser\"",
Expand Down Expand Up @@ -114,6 +135,7 @@
"bundlewatch": "^0.4.1",
"jsdom": "26.1.0",
"minimatch": "^10.0.3",
"tsdown": "catalog:repo",
"webpack-merge": "^5.10.0"
},
"engines": {
Expand Down
16 changes: 16 additions & 0 deletions packages/clerk-js/src/__tests__/__snapshots__/exports.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`module exports > bundled export (bundled.ts) > should have the expected shape 1`] = `
[
"ClerkJS",
"__brand",
"version",
]
`;

exports[`module exports > server export (server.ts) > should have the expected shape 1`] = `
[
"__brand",
"version",
]
`;
45 changes: 45 additions & 0 deletions packages/clerk-js/src/__tests__/exports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest';

import { js } from '../bundled';
import { js as serverJs } from '../server';

describe('module exports', () => {
describe('bundled export (bundled.ts)', () => {
it('should have the expected shape', () => {
expect(Object.keys(js).sort()).toMatchSnapshot();
});

it('should include __brand marker', () => {
expect((js as any).__brand).toBe('__clerkJS');
});

it('should include ClerkJS constructor', () => {
expect((js as any).ClerkJS).toBeDefined();
expect(typeof (js as any).ClerkJS).toBe('function');
});

it('should include version', () => {
expect((js as any).version).toBeDefined();
expect(typeof (js as any).version).toBe('string');
});
});

describe('server export (server.ts)', () => {
it('should have the expected shape', () => {
expect(Object.keys(serverJs).sort()).toMatchSnapshot();
});

it('should include __brand marker', () => {
expect((serverJs as any).__brand).toBe('__clerkJS');
});

it('should NOT include ClerkJS constructor', () => {
expect((serverJs as any).ClerkJS).toBeUndefined();
});

it('should include version', () => {
expect((serverJs as any).version).toBeDefined();
expect(typeof (serverJs as any).version).toBe('string');
});
});
});
27 changes: 27 additions & 0 deletions packages/clerk-js/src/bundled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Js } from './internal';
import { JS_BRAND } from './internal';

import { Clerk } from './core/clerk';

declare const PACKAGE_VERSION: string;

/**
* JS object for bundled Clerk JS.
* Pass this to ClerkProvider to use the bundled clerk-js instead of loading from CDN.
*
* @example
* ```tsx
* import { js } from '@clerk/clerk-js/bundled';
*
* <ClerkProvider js={js}>
* ...
* </ClerkProvider>
* ```
*/
export const js = {
__brand: JS_BRAND,
version: PACKAGE_VERSION,
ClerkJS: Clerk,
} as unknown as Js;

export type { Js } from './internal';
5 changes: 5 additions & 0 deletions packages/clerk-js/src/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Entry point for dynamic import of ClerkJS constructor.
* Used by the SDK when the js prop is a server-safe marker (without ClerkJS constructor).
*/
export { Clerk as ClerkJS } from './core/clerk';
35 changes: 35 additions & 0 deletions packages/clerk-js/src/internal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { BrowserClerkConstructor } from '@clerk/shared/types';

declare const Tags: unique symbol;
type Tagged<BaseType, Tag extends PropertyKey> = BaseType & { [Tags]: { [K in Tag]: void } };

/**
* Runtime brand value to identify valid JS objects
*/
export const JS_BRAND = '__clerkJS' as const;

/**
* Js type that carries type information via phantom property
* Tagged to ensure only official js objects from @clerk/clerk-js can be used
*
* ClerkJS is optional to support server-safe marker exports (react-server condition).
* When ClerkJS is absent, the SDK will dynamically import it.
*/
export type Js = Tagged<
{
/**
* Runtime brand to identify valid JS objects
*/
__brand: typeof JS_BRAND;
/**
* ClerkJS constructor. Optional to support server-safe marker exports.
* When absent (e.g., in React Server Components), the SDK resolves it via dynamic import.
*/
ClerkJS?: BrowserClerkConstructor;
/**
* Version of the JS package (for potential future use)
*/
version?: string;
},
'ClerkJS'
>;
28 changes: 28 additions & 0 deletions packages/clerk-js/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Js } from './internal';
import { JS_BRAND } from './internal';

declare const PACKAGE_VERSION: string;

/**
* Server-safe JS marker for React Server Components.
*
* This export does not include the ClerkJS constructor, making it safe to import
* in server components. The constructor is resolved via dynamic import when needed.
*
* @example
* ```tsx
* // app/layout.tsx (server component)
* import { ClerkProvider } from '@clerk/nextjs';
* import { js } from '@clerk/clerk-js/bundled';
*
* export default function Layout({ children }) {
* return <ClerkProvider js={js}>{children}</ClerkProvider>;
* }
* ```
*/
export const js = {
__brand: JS_BRAND,
version: PACKAGE_VERSION,
} as unknown as Js;

export type { Js } from './internal';
2 changes: 1 addition & 1 deletion packages/clerk-js/tsconfig.declarations.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +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/internal/index.ts", "src/**/*.d.ts"]
}
34 changes: 34 additions & 0 deletions packages/clerk-js/tsdown.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Options } from 'tsdown';
import { defineConfig } from 'tsdown';

import clerkJsPackage from './package.json' with { type: 'json' };

export default defineConfig(({ watch }) => {
const common = {
dts: true,
sourcemap: true,
clean: false,
target: 'es2022',
platform: 'browser',
external: ['@clerk/shared'],
format: ['esm'],
minify: false,
define: {
PACKAGE_NAME: `"${clerkJsPackage.name}"`,
PACKAGE_VERSION: `"${clerkJsPackage.version}"`,
__DEV__: `${watch}`,
__BUILD_DISABLE_RHC__: JSON.stringify(false),
},
} satisfies Options;

return [
// Build internal types, server (marker), entry point, and bundled wrapper
// These are unbundled - the user's bundler handles dependency resolution
{
...common,
entry: ['./src/internal/index.ts', './src/server.ts', './src/entry.ts', './src/bundled.ts'],
outDir: './dist/esm',
unbundle: true,
},
];
});
2 changes: 2 additions & 0 deletions packages/clerk-js/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default defineConfig({
__BUILD_VARIANT_CHIPS__: JSON.stringify(false),
__PKG_NAME__: JSON.stringify('@clerk/clerk-js'),
__PKG_VERSION__: JSON.stringify('test'),
PACKAGE_NAME: JSON.stringify('@clerk/clerk-js'),
PACKAGE_VERSION: JSON.stringify('0.0.0-test'),
},
test: {
coverage: {
Expand Down
Loading
Loading