Skip to content

Commit f7472e2

Browse files
alexcarpenterbrkalow
andauthoredNov 8, 2024··
fix(elements): Fix elements router usage (#4513)
Co-authored-by: Bryce Kalow <bryce@clerk.dev>
1 parent f5f6c4d commit f7472e2

File tree

18 files changed

+119
-133
lines changed

18 files changed

+119
-133
lines changed
 

Diff for: ‎.changeset/empty-dots-confess.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@clerk/elements': patch
3+
'@clerk/nextjs': patch
4+
'@clerk/shared': patch
5+
'@clerk/types': patch
6+
'@clerk/clerk-js': patch
7+
---
8+
9+
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`.

Diff for: ‎package-lock.json

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎packages/clerk-js/bundlewatch.config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{ "path": "./dist/clerk.js", "maxSize": "707kB" },
44
{ "path": "./dist/clerk.browser.js", "maxSize": "75kB" },
55
{ "path": "./dist/clerk.headless.js", "maxSize": "48kB" },
6-
{ "path": "./dist/ui-common*.js", "maxSize": "87KB" },
6+
{ "path": "./dist/ui-common*.js", "maxSize": "88KB" },
77
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
88
{ "path": "./dist/coinbase*.js", "maxSize": "58KB" },
99
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },

Diff for: ‎packages/clerk-js/jest.setup.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ if (typeof window !== 'undefined') {
3535

3636
global.__PKG_NAME__ = '';
3737
global.__PKG_VERSION__ = '';
38+
global.BUILD_ENABLE_NEW_COMPONENTS = '';
3839

3940
//@ts-expect-error
4041
global.IntersectionObserver = class IntersectionObserver {

Diff for: ‎packages/clerk-js/rspack.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const common = ({ mode }) => {
4242
__DEV__: isDevelopment(mode),
4343
__PKG_VERSION__: JSON.stringify(packageJSON.version),
4444
__PKG_NAME__: JSON.stringify(packageJSON.name),
45+
BUILD_ENABLE_NEW_COMPONENTS: JSON.stringify(process.env.BUILD_ENABLE_NEW_COMPONENTS),
4546
}),
4647
new rspack.EnvironmentPlugin({
4748
CLERK_ENV: mode,

Diff for: ‎packages/clerk-js/src/core/clerk.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,14 @@ export class Clerk implements ClerkInterface {
340340
this.#loaded = await this.#loadInNonStandardBrowser();
341341
}
342342

343-
if (clerkIsLoaded(this)) {
344-
this.__experimental_ui = new UI({
345-
router: this.#options.__experimental_router,
346-
clerk: this,
347-
options: this.#options,
348-
});
343+
if (BUILD_ENABLE_NEW_COMPONENTS) {
344+
if (clerkIsLoaded(this)) {
345+
this.__experimental_ui = new UI({
346+
router: this.#options.__experimental_router,
347+
clerk: this,
348+
options: this.#options,
349+
});
350+
}
349351
}
350352
};
351353

Diff for: ‎packages/clerk-js/src/global.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ declare module '@clerk/ui/styles.css' {
22
const content: string;
33
export default content;
44
}
5+
6+
declare const BUILD_ENABLE_NEW_COMPONENTS: string;

Diff for: ‎packages/clerk-js/src/ui/new/index.tsx

+17-15
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,23 @@ export class UI {
3333
this.clerk = clerk;
3434
this.options = options;
3535

36-
// register components
37-
this.register('SignIn', {
38-
type: 'component',
39-
load: () =>
40-
import(/* webpackChunkName: "rebuild--sign-in" */ '@clerk/ui/sign-in').then(({ SignIn }) => ({
41-
default: SignIn,
42-
})),
43-
});
44-
this.register('SignUp', {
45-
type: 'component',
46-
load: () =>
47-
import(/* webpackChunkName: "rebuild--sign-up" */ '@clerk/ui/sign-up').then(({ SignUp }) => ({
48-
default: SignUp,
49-
})),
50-
});
36+
if (BUILD_ENABLE_NEW_COMPONENTS) {
37+
// register components
38+
this.register('SignIn', {
39+
type: 'component',
40+
load: () =>
41+
import(/* webpackChunkName: "rebuild--sign-in" */ '@clerk/ui/sign-in').then(({ SignIn }) => ({
42+
default: SignIn,
43+
})),
44+
});
45+
this.register('SignUp', {
46+
type: 'component',
47+
load: () =>
48+
import(/* webpackChunkName: "rebuild--sign-up" */ '@clerk/ui/sign-up').then(({ SignUp }) => ({
49+
default: SignUp,
50+
})),
51+
});
52+
}
5153
}
5254

5355
// Mount a component from the registry

Diff for: ‎packages/clerk-js/turbo.json

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"extends": ["//"],
33
"tasks": {
44
"build": {
5+
"env": ["BUILD_ENABLE_NEW_COMPONENTS"],
56
"inputs": [
67
"*.d.ts",
78
"bundlewatch.config.json",

Diff for: ‎packages/elements/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
"test:cache:clear": "jest --clearCache --useStderr"
7272
},
7373
"dependencies": {
74-
"@clerk/shared": "2.11.5",
7574
"@clerk/types": "^4.30.0",
7675
"@radix-ui/react-form": "^0.1.0",
7776
"@radix-ui/react-slot": "^1.1.0",
@@ -82,6 +81,7 @@
8281
"devDependencies": {
8382
"@clerk/clerk-react": "5.15.1",
8483
"@clerk/eslint-config-custom": "*",
84+
"@clerk/shared": "2.11.5",
8585
"@statelyai/inspect": "^0.4.0",
8686
"@types/node": "^18.19.33",
8787
"@types/react": "*",
@@ -94,6 +94,8 @@
9494
"typescript": "*"
9595
},
9696
"peerDependencies": {
97+
"@clerk/shared": "2.x",
98+
"next": "^13.5.4 || ^14.0.3 || ^15",
9799
"react": "^18.0.0 || ^19.0.0-beta",
98100
"react-dom": "^18.0.0 || ^19.0.0-beta"
99101
},

Diff for: ‎packages/elements/src/internals/machines/third-party/third-party.actors.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ export const handleRedirectCallback = fromCallback<AnyEventObject, HandleRedirec
8080
{
8181
signInForceRedirectUrl: ClerkJSNavigationEvent.complete,
8282
signInFallbackRedirectUrl: ClerkJSNavigationEvent.complete,
83-
signUpForceRedirectUrl: ClerkJSNavigationEvent.signUp,
84-
signUpFallbackRedirectUrl: ClerkJSNavigationEvent.signUp,
83+
signUpForceRedirectUrl: ClerkJSNavigationEvent.complete,
84+
signUpFallbackRedirectUrl: ClerkJSNavigationEvent.complete,
8585
continueSignUpUrl: ClerkJSNavigationEvent.continue,
8686
firstFactorUrl: ClerkJSNavigationEvent.signIn,
8787
resetPasswordUrl: ClerkJSNavigationEvent.resetPassword,

Diff for: ‎packages/elements/src/react/router/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export { Route, Router, useClerkRouter } from '@clerk/shared/router';
1+
export { Route, Router, useClerkRouter, ClerkHostRouterContext } from '@clerk/shared/router';
22
export { useVirtualRouter } from './virtual';
3+
export { useNextRouter } from './next';

Diff for: ‎packages/elements/src/react/router/next.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { ClerkHostRouter } from '@clerk/types';
2+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
3+
4+
import { NEXT_WINDOW_HISTORY_SUPPORT_VERSION } from '~/internals/constants';
5+
6+
import { usePathnameWithoutCatchAll } from '../utils/path-inference/next';
7+
8+
/**
9+
* Clerk Elements router integration with Next.js's router.
10+
*/
11+
export const useNextRouter = (): ClerkHostRouter => {
12+
const router = useRouter();
13+
const pathname = usePathname();
14+
const searchParams = useSearchParams();
15+
const inferredBasePath = usePathnameWithoutCatchAll();
16+
17+
// The window.history APIs seem to prevent Next.js from triggering a full page re-render, allowing us to
18+
// preserve internal state between steps.
19+
const canUseWindowHistoryAPIs =
20+
typeof window !== 'undefined' && window.next && window.next.version >= NEXT_WINDOW_HISTORY_SUPPORT_VERSION;
21+
22+
return {
23+
mode: 'path',
24+
name: 'NextRouter',
25+
push: (path: string) => router.push(path),
26+
replace: (path: string) =>
27+
canUseWindowHistoryAPIs ? window.history.replaceState(null, '', path) : router.replace(path),
28+
shallowPush(path: string) {
29+
canUseWindowHistoryAPIs ? window.history.pushState(null, '', path) : router.push(path, {});
30+
},
31+
pathname: () => pathname,
32+
searchParams: () => searchParams,
33+
inferredBasePath: () => inferredBasePath,
34+
};
35+
};

Diff for: ‎packages/elements/src/react/sign-in/root.tsx

+16-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useClerk } from '@clerk/shared/react';
2-
import { useClerkHostRouter } from '@clerk/shared/router';
32
import { eventComponentMounted } from '@clerk/shared/telemetry';
43
import { useSelector } from '@xstate/react';
54
import React, { useEffect } from 'react';
@@ -10,11 +9,10 @@ import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.
109
import type { SignInRouterInitEvent } from '~/internals/machines/sign-in';
1110
import { SignInRouterMachine } from '~/internals/machines/sign-in';
1211
import { inspect } from '~/internals/utils/inspector';
13-
import { Router, useClerkRouter, useVirtualRouter } from '~/react/router';
12+
import { ClerkHostRouterContext, Router, useClerkRouter, useNextRouter, useVirtualRouter } from '~/react/router';
1413
import { SignInRouterCtx } from '~/react/sign-in/context';
1514

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

1917
type SignInFlowProviderProps = {
2018
children: React.ReactNode;
@@ -118,9 +116,9 @@ export function SignInRoot({
118116
routing = ROUTING.path,
119117
}: SignInRootProps): JSX.Element | null {
120118
const clerk = useClerk();
121-
const router = (routing === ROUTING.virtual ? useVirtualRouter : useClerkHostRouter)();
119+
const router = (routing === ROUTING.virtual ? useVirtualRouter : useNextRouter)();
122120
const pathname = router.pathname();
123-
const inferredPath = removeOptionalCatchAllSegment(pathname);
121+
const inferredPath = router.inferredBasePath?.();
124122
const path = pathProp || inferredPath || SIGN_IN_DEFAULT_BASE_PATH;
125123
const isRootPath = path === pathname;
126124

@@ -134,19 +132,18 @@ export function SignInRoot({
134132
);
135133

136134
return (
137-
<Router
138-
basePath={path}
139-
router={router}
140-
>
141-
<FormStoreProvider>
142-
<SignInFlowProvider
143-
exampleMode={exampleMode}
144-
fallback={fallback}
145-
isRootPath={isRootPath}
146-
>
147-
{children}
148-
</SignInFlowProvider>
149-
</FormStoreProvider>
150-
</Router>
135+
<ClerkHostRouterContext.Provider value={router}>
136+
<Router basePath={path}>
137+
<FormStoreProvider>
138+
<SignInFlowProvider
139+
exampleMode={exampleMode}
140+
fallback={fallback}
141+
isRootPath={isRootPath}
142+
>
143+
{children}
144+
</SignInFlowProvider>
145+
</FormStoreProvider>
146+
</Router>
147+
</ClerkHostRouterContext.Provider>
151148
);
152149
}

Diff for: ‎packages/elements/src/react/sign-up/root.tsx

+16-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useClerk } from '@clerk/shared/react';
2-
import { useClerkHostRouter } from '@clerk/shared/router';
32
import { eventComponentMounted } from '@clerk/shared/telemetry';
43
import { useSelector } from '@xstate/react';
54
import { useEffect } from 'react';
@@ -10,11 +9,10 @@ import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form.
109
import type { SignUpRouterInitEvent } from '~/internals/machines/sign-up';
1110
import { SignUpRouterMachine } from '~/internals/machines/sign-up';
1211
import { inspect } from '~/internals/utils/inspector';
13-
import { Router, useClerkRouter, useVirtualRouter } from '~/react/router';
12+
import { ClerkHostRouterContext, Router, useClerkRouter, useNextRouter, useVirtualRouter } from '~/react/router';
1413
import { SignUpRouterCtx } from '~/react/sign-up/context';
1514

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

1917
type SignUpFlowProviderProps = {
2018
children: React.ReactNode;
@@ -117,9 +115,9 @@ export function SignUpRoot({
117115
routing = ROUTING.path,
118116
}: SignUpRootProps): JSX.Element | null {
119117
const clerk = useClerk();
120-
const router = (routing === ROUTING.virtual ? useVirtualRouter : useClerkHostRouter)();
118+
const router = (routing === ROUTING.virtual ? useVirtualRouter : useNextRouter)();
121119
const pathname = router.pathname();
122-
const inferredPath = removeOptionalCatchAllSegment(pathname);
120+
const inferredPath = router.inferredBasePath?.();
123121
const path = pathProp || inferredPath || SIGN_UP_DEFAULT_BASE_PATH;
124122
const isRootPath = path === pathname;
125123

@@ -133,19 +131,18 @@ export function SignUpRoot({
133131
);
134132

135133
return (
136-
<Router
137-
basePath={path}
138-
router={router}
139-
>
140-
<FormStoreProvider>
141-
<SignUpFlowProvider
142-
exampleMode={exampleMode}
143-
fallback={fallback}
144-
isRootPath={isRootPath}
145-
>
146-
{children}
147-
</SignUpFlowProvider>
148-
</FormStoreProvider>
149-
</Router>
134+
<ClerkHostRouterContext.Provider value={router}>
135+
<Router basePath={path}>
136+
<FormStoreProvider>
137+
<SignUpFlowProvider
138+
exampleMode={exampleMode}
139+
fallback={fallback}
140+
isRootPath={isRootPath}
141+
>
142+
{children}
143+
</SignUpFlowProvider>
144+
</FormStoreProvider>
145+
</Router>
146+
</ClerkHostRouterContext.Provider>
150147
);
151148
}

Diff for: ‎packages/nextjs/src/app-router/client/ClerkProvider.tsx

+1-35
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
'use client';
22
import { ClerkProvider as ReactClerkProvider } from '@clerk/clerk-react';
3-
import type { ClerkHostRouter } from '@clerk/shared/router';
4-
import { ClerkHostRouterContext } from '@clerk/shared/router';
53
import { useRouter } from 'next/navigation';
64
import React, { useEffect, useTransition } from 'react';
75

@@ -25,40 +23,9 @@ declare global {
2523
}
2624
}
2725

28-
// The version that Next added support for the window.history.pushState and replaceState APIs.
29-
// ref: https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate
30-
const NEXT_WINDOW_HISTORY_SUPPORT_VERSION = '14.1.0';
31-
32-
/**
33-
* Clerk router integration with Next.js's router.
34-
*/
35-
const useNextRouter = (): ClerkHostRouter => {
36-
const router = useRouter();
37-
38-
// The window.history APIs seem to prevent Next.js from triggering a full page re-render, allowing us to
39-
// preserve internal state between steps.
40-
const canUseWindowHistoryAPIs =
41-
typeof window !== 'undefined' && window.next && window.next.version >= NEXT_WINDOW_HISTORY_SUPPORT_VERSION;
42-
43-
return {
44-
mode: 'path',
45-
name: 'NextRouter',
46-
push: (path: string) => router.push(path),
47-
replace: (path: string) =>
48-
canUseWindowHistoryAPIs ? window.history.replaceState(null, '', path) : router.replace(path),
49-
shallowPush(path: string) {
50-
canUseWindowHistoryAPIs ? window.history.pushState(null, '', path) : router.push(path, {});
51-
},
52-
pathname: () => (typeof window !== 'undefined' ? window.location.pathname : ''),
53-
searchParams: () =>
54-
typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams(),
55-
};
56-
};
57-
5826
export const ClientClerkProvider = (props: NextClerkProviderProps) => {
5927
const { __unstable_invokeMiddlewareOnAuthStateChange = true, children } = props;
6028
const router = useRouter();
61-
const clerkRouter = useNextRouter();
6229
const push = useAwaitablePush();
6330
const replace = useAwaitableReplace();
6431
const [isPending, startTransition] = useTransition();
@@ -119,7 +86,6 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => {
11986

12087
const mergedProps = mergeNextClerkPropsWithEnv({
12188
...props,
122-
__experimental_router: clerkRouter,
12389
routerPush: push,
12490
routerReplace: replace,
12591
});
@@ -128,7 +94,7 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => {
12894
<ClerkNextOptionsProvider options={mergedProps}>
12995
<ReactClerkProvider {...mergedProps}>
13096
<ClerkJSScript router='app' />
131-
<ClerkHostRouterContext.Provider value={clerkRouter}>{children}</ClerkHostRouterContext.Provider>
97+
{children}
13298
</ReactClerkProvider>
13399
</ClerkNextOptionsProvider>
134100
);

Diff for: ‎packages/nextjs/src/pages/ClerkProvider.tsx

-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { ClerkProvider as ReactClerkProvider } from '@clerk/clerk-react';
22
// Override Clerk React error thrower to show that errors come from @clerk/nextjs
33
import { setClerkJsLoadingErrorPackageName, setErrorThrowerOptions } from '@clerk/clerk-react/internal';
4-
import type { ClerkHostRouter } from '@clerk/shared/router';
54
import { useRouter } from 'next/router';
65
import React from 'react';
76

@@ -16,40 +15,9 @@ import { removeBasePath } from '../utils/removeBasePath';
1615
setErrorThrowerOptions({ packageName: PACKAGE_NAME });
1716
setClerkJsLoadingErrorPackageName(PACKAGE_NAME);
1817

19-
// The version that Next added support for the window.history.pushState and replaceState APIs.
20-
// ref: https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate
21-
const NEXT_WINDOW_HISTORY_SUPPORT_VERSION = '14.1.0';
22-
23-
/**
24-
* Clerk router integration with Next.js's router.
25-
*/
26-
const useNextRouter = (): ClerkHostRouter => {
27-
const router = useRouter();
28-
29-
// The window.history APIs seem to prevent Next.js from triggering a full page re-render, allowing us to
30-
// preserve internal state between steps.
31-
const canUseWindowHistoryAPIs =
32-
typeof window !== 'undefined' && window.next && window.next.version >= NEXT_WINDOW_HISTORY_SUPPORT_VERSION;
33-
34-
return {
35-
mode: 'path',
36-
name: 'NextRouter',
37-
push: (path: string) => router.push(path),
38-
replace: (path: string) =>
39-
canUseWindowHistoryAPIs ? window.history.replaceState(null, '', path) : router.replace(path),
40-
shallowPush(path: string) {
41-
canUseWindowHistoryAPIs ? window.history.pushState(null, '', path) : router.push(path, {});
42-
},
43-
pathname: () => (typeof window !== 'undefined' ? window.location.pathname : ''),
44-
searchParams: () =>
45-
typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams(),
46-
};
47-
};
48-
4918
export function ClerkProvider({ children, ...props }: NextClerkProviderProps): JSX.Element {
5019
const { __unstable_invokeMiddlewareOnAuthStateChange = true } = props;
5120
const { push, replace } = useRouter();
52-
const clerkRouter = useNextRouter();
5321
ReactClerkProvider.displayName = 'ReactClerkProvider';
5422

5523
useSafeLayoutEffect(() => {
@@ -71,7 +39,6 @@ export function ClerkProvider({ children, ...props }: NextClerkProviderProps): J
7139
const replaceNavigate = (to: string) => replace(removeBasePath(to));
7240
const mergedProps = mergeNextClerkPropsWithEnv({
7341
...props,
74-
__experimental_router: clerkRouter,
7542
routerPush: navigate,
7643
routerReplace: replaceNavigate,
7744
});

Diff for: ‎packages/types/src/router.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export type ClerkHostRouter = {
1111
replace: (path: string) => void;
1212
searchParams: () => URLSearchParams;
1313
shallowPush: (path: string) => void;
14+
inferredBasePath?: () => string;
1415
};

0 commit comments

Comments
 (0)
Please sign in to comment.