Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .changeset/empty-dots-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@clerk/elements': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
'@clerk/types': patch
'@clerk/clerk-js': patch
---

Fixes issues in `ClerkRouter` that were causing inaccurate pathnames within Elements flows. Also fixes a dependency issue where `@clerk/elements` was pulling in the wrong version of `@clerk/shared`.
4 changes: 3 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{ "path": "./dist/clerk.js", "maxSize": "707kB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "75kB" },
{ "path": "./dist/clerk.headless.js", "maxSize": "48kB" },
{ "path": "./dist/ui-common*.js", "maxSize": "87KB" },
{ "path": "./dist/ui-common*.js", "maxSize": "88KB" },
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "58KB" },
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
Expand Down
1 change: 1 addition & 0 deletions packages/clerk-js/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ if (typeof window !== 'undefined') {

global.__PKG_NAME__ = '';
global.__PKG_VERSION__ = '';
global.BUILD_ENABLE_NEW_COMPONENTS = '';

//@ts-expect-error
global.IntersectionObserver = class IntersectionObserver {
Expand Down
1 change: 1 addition & 0 deletions packages/clerk-js/rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const common = ({ mode }) => {
__DEV__: isDevelopment(mode),
__PKG_VERSION__: JSON.stringify(packageJSON.version),
__PKG_NAME__: JSON.stringify(packageJSON.name),
BUILD_ENABLE_NEW_COMPONENTS: JSON.stringify(process.env.BUILD_ENABLE_NEW_COMPONENTS),
}),
new rspack.EnvironmentPlugin({
CLERK_ENV: mode,
Expand Down
14 changes: 8 additions & 6 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,14 @@ export class Clerk implements ClerkInterface {
this.#loaded = await this.#loadInNonStandardBrowser();
}

if (clerkIsLoaded(this)) {
this.__experimental_ui = new UI({
router: this.#options.__experimental_router,
clerk: this,
options: this.#options,
});
if (BUILD_ENABLE_NEW_COMPONENTS) {
if (clerkIsLoaded(this)) {
this.__experimental_ui = new UI({
router: this.#options.__experimental_router,
clerk: this,
options: this.#options,
});
}
}
};

Expand Down
2 changes: 2 additions & 0 deletions packages/clerk-js/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ declare module '@clerk/ui/styles.css' {
const content: string;
export default content;
}

declare const BUILD_ENABLE_NEW_COMPONENTS: string;
32 changes: 17 additions & 15 deletions packages/clerk-js/src/ui/new/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,23 @@ export class UI {
this.clerk = clerk;
this.options = options;

// register components
this.register('SignIn', {
type: 'component',
load: () =>
import(/* webpackChunkName: "rebuild--sign-in" */ '@clerk/ui/sign-in').then(({ SignIn }) => ({
default: SignIn,
})),
});
this.register('SignUp', {
type: 'component',
load: () =>
import(/* webpackChunkName: "rebuild--sign-up" */ '@clerk/ui/sign-up').then(({ SignUp }) => ({
default: SignUp,
})),
});
if (BUILD_ENABLE_NEW_COMPONENTS) {
// register components
this.register('SignIn', {
type: 'component',
load: () =>
import(/* webpackChunkName: "rebuild--sign-in" */ '@clerk/ui/sign-in').then(({ SignIn }) => ({
default: SignIn,
})),
});
this.register('SignUp', {
type: 'component',
load: () =>
import(/* webpackChunkName: "rebuild--sign-up" */ '@clerk/ui/sign-up').then(({ SignUp }) => ({
default: SignUp,
})),
});
}
}

// Mount a component from the registry
Expand Down
1 change: 1 addition & 0 deletions packages/clerk-js/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": ["//"],
"tasks": {
"build": {
"env": ["BUILD_ENABLE_NEW_COMPONENTS"],
"inputs": [
"*.d.ts",
"bundlewatch.config.json",
Expand Down
4 changes: 3 additions & 1 deletion packages/elements/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
"test:cache:clear": "jest --clearCache --useStderr"
},
"dependencies": {
"@clerk/shared": "2.11.5",
"@clerk/types": "^4.30.0",
"@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-slot": "^1.1.0",
Expand All @@ -82,6 +81,7 @@
"devDependencies": {
"@clerk/clerk-react": "5.15.1",
"@clerk/eslint-config-custom": "*",
"@clerk/shared": "2.11.5",
"@statelyai/inspect": "^0.4.0",
"@types/node": "^18.19.33",
"@types/react": "*",
Expand All @@ -94,6 +94,8 @@
"typescript": "*"
},
"peerDependencies": {
"@clerk/shared": "2.x",
"next": "^13.5.4 || ^14.0.3 || ^15",
"react": "^18.0.0 || ^19.0.0-beta",
"react-dom": "^18.0.0 || ^19.0.0-beta"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ export const handleRedirectCallback = fromCallback<AnyEventObject, HandleRedirec
{
signInForceRedirectUrl: ClerkJSNavigationEvent.complete,
signInFallbackRedirectUrl: ClerkJSNavigationEvent.complete,
signUpForceRedirectUrl: ClerkJSNavigationEvent.signUp,
signUpFallbackRedirectUrl: ClerkJSNavigationEvent.signUp,
signUpForceRedirectUrl: ClerkJSNavigationEvent.complete,
signUpFallbackRedirectUrl: ClerkJSNavigationEvent.complete,
continueSignUpUrl: ClerkJSNavigationEvent.continue,
firstFactorUrl: ClerkJSNavigationEvent.signIn,
resetPasswordUrl: ClerkJSNavigationEvent.resetPassword,
Expand Down
3 changes: 2 additions & 1 deletion packages/elements/src/react/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { Route, Router, useClerkRouter } from '@clerk/shared/router';
export { Route, Router, useClerkRouter, ClerkHostRouterContext } from '@clerk/shared/router';
export { useVirtualRouter } from './virtual';
export { useNextRouter } from './next';
35 changes: 35 additions & 0 deletions packages/elements/src/react/router/next.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ClerkHostRouter } from '@clerk/types';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';

import { NEXT_WINDOW_HISTORY_SUPPORT_VERSION } from '~/internals/constants';

import { usePathnameWithoutCatchAll } from '../utils/path-inference/next';

/**
* Clerk Elements router integration with Next.js's router.
*/
export const useNextRouter = (): ClerkHostRouter => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const inferredBasePath = usePathnameWithoutCatchAll();

// The window.history APIs seem to prevent Next.js from triggering a full page re-render, allowing us to
// preserve internal state between steps.
const canUseWindowHistoryAPIs =
typeof window !== 'undefined' && window.next && window.next.version >= NEXT_WINDOW_HISTORY_SUPPORT_VERSION;

return {
mode: 'path',
name: 'NextRouter',
push: (path: string) => router.push(path),
replace: (path: string) =>
canUseWindowHistoryAPIs ? window.history.replaceState(null, '', path) : router.replace(path),
shallowPush(path: string) {
canUseWindowHistoryAPIs ? window.history.pushState(null, '', path) : router.push(path, {});
},
pathname: () => pathname,
searchParams: () => searchParams,
inferredBasePath: () => inferredBasePath,
};
};
35 changes: 16 additions & 19 deletions packages/elements/src/react/sign-in/root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useClerk } from '@clerk/shared/react';
import { useClerkHostRouter } from '@clerk/shared/router';
import { eventComponentMounted } from '@clerk/shared/telemetry';
import { useSelector } from '@xstate/react';
import React, { useEffect } from 'react';
Expand All @@ -10,11 +9,10 @@ import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.
import type { SignInRouterInitEvent } from '~/internals/machines/sign-in';
import { SignInRouterMachine } from '~/internals/machines/sign-in';
import { inspect } from '~/internals/utils/inspector';
import { Router, useClerkRouter, useVirtualRouter } from '~/react/router';
import { ClerkHostRouterContext, Router, useClerkRouter, useNextRouter, useVirtualRouter } from '~/react/router';
import { SignInRouterCtx } from '~/react/sign-in/context';

import { Form } from '../common/form';
import { removeOptionalCatchAllSegment } from '../utils/path-inference/utils';

type SignInFlowProviderProps = {
children: React.ReactNode;
Expand Down Expand Up @@ -118,9 +116,9 @@ export function SignInRoot({
routing = ROUTING.path,
}: SignInRootProps): JSX.Element | null {
const clerk = useClerk();
const router = (routing === ROUTING.virtual ? useVirtualRouter : useClerkHostRouter)();
const router = (routing === ROUTING.virtual ? useVirtualRouter : useNextRouter)();
const pathname = router.pathname();
const inferredPath = removeOptionalCatchAllSegment(pathname);
const inferredPath = router.inferredBasePath?.();
const path = pathProp || inferredPath || SIGN_IN_DEFAULT_BASE_PATH;
const isRootPath = path === pathname;

Expand All @@ -134,19 +132,18 @@ export function SignInRoot({
);

return (
<Router
basePath={path}
router={router}
>
<FormStoreProvider>
<SignInFlowProvider
exampleMode={exampleMode}
fallback={fallback}
isRootPath={isRootPath}
>
{children}
</SignInFlowProvider>
</FormStoreProvider>
</Router>
<ClerkHostRouterContext.Provider value={router}>
<Router basePath={path}>
<FormStoreProvider>
<SignInFlowProvider
exampleMode={exampleMode}
fallback={fallback}
isRootPath={isRootPath}
>
{children}
</SignInFlowProvider>
</FormStoreProvider>
</Router>
</ClerkHostRouterContext.Provider>
);
}
35 changes: 16 additions & 19 deletions packages/elements/src/react/sign-up/root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useClerk } from '@clerk/shared/react';
import { useClerkHostRouter } from '@clerk/shared/router';
import { eventComponentMounted } from '@clerk/shared/telemetry';
import { useSelector } from '@xstate/react';
import { useEffect } from 'react';
Expand All @@ -10,11 +9,10 @@ import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.
import type { SignUpRouterInitEvent } from '~/internals/machines/sign-up';
import { SignUpRouterMachine } from '~/internals/machines/sign-up';
import { inspect } from '~/internals/utils/inspector';
import { Router, useClerkRouter, useVirtualRouter } from '~/react/router';
import { ClerkHostRouterContext, Router, useClerkRouter, useNextRouter, useVirtualRouter } from '~/react/router';
import { SignUpRouterCtx } from '~/react/sign-up/context';

import { Form } from '../common/form';
import { removeOptionalCatchAllSegment } from '../utils/path-inference/utils';

type SignUpFlowProviderProps = {
children: React.ReactNode;
Expand Down Expand Up @@ -117,9 +115,9 @@ export function SignUpRoot({
routing = ROUTING.path,
}: SignUpRootProps): JSX.Element | null {
const clerk = useClerk();
const router = (routing === ROUTING.virtual ? useVirtualRouter : useClerkHostRouter)();
const router = (routing === ROUTING.virtual ? useVirtualRouter : useNextRouter)();
const pathname = router.pathname();
const inferredPath = removeOptionalCatchAllSegment(pathname);
const inferredPath = router.inferredBasePath?.();
const path = pathProp || inferredPath || SIGN_UP_DEFAULT_BASE_PATH;
const isRootPath = path === pathname;

Expand All @@ -133,19 +131,18 @@ export function SignUpRoot({
);

return (
<Router
basePath={path}
router={router}
>
<FormStoreProvider>
<SignUpFlowProvider
exampleMode={exampleMode}
fallback={fallback}
isRootPath={isRootPath}
>
{children}
</SignUpFlowProvider>
</FormStoreProvider>
</Router>
<ClerkHostRouterContext.Provider value={router}>
<Router basePath={path}>
<FormStoreProvider>
<SignUpFlowProvider
exampleMode={exampleMode}
fallback={fallback}
isRootPath={isRootPath}
>
{children}
</SignUpFlowProvider>
</FormStoreProvider>
</Router>
</ClerkHostRouterContext.Provider>
);
}
Loading
Loading