Skip to content

Commit 31da6d0

Browse files
committed
fix(nextjs): replace __internal_skipScripts with scriptsSlot composition pattern
Replace the boolean `__internal_skipScripts` prop with a `__internal_scriptsSlot` React node prop. The server provider now passes dynamic scripts as a slot prop instead of rendering them as sibling children, preventing duplicate script rendering in nested `<ClerkProvider dynamic>` scenarios.
1 parent 0c79adb commit 31da6d0

4 files changed

Lines changed: 14 additions & 15 deletions

File tree

packages/nextjs/src/app-router/client/ClerkProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const LazyCreateKeylessApplication = dynamic(() =>
3232
);
3333

3434
const NextClientClerkProvider = <TUi extends Ui = Ui>(props: NextClerkProviderProps<TUi>) => {
35-
const { __internal_invokeMiddlewareOnAuthStateChange = true, __internal_skipScripts = false, children } = props;
35+
const { __internal_invokeMiddlewareOnAuthStateChange = true, __internal_scriptsSlot, children } = props;
3636
const router = useRouter();
3737
const push = useAwaitablePush();
3838
const replace = useAwaitableReplace();
@@ -109,7 +109,7 @@ const NextClientClerkProvider = <TUi extends Ui = Ui>(props: NextClerkProviderPr
109109
<ClerkNextOptionsProvider options={mergedProps}>
110110
<ReactClerkProvider {...mergedProps}>
111111
<RouterTelemetry />
112-
{!__internal_skipScripts && <ClerkScripts />}
112+
{__internal_scriptsSlot ?? <ClerkScripts />}
113113
{children}
114114
</ReactClerkProvider>
115115
</ClerkNextOptionsProvider>

packages/nextjs/src/app-router/server/ClerkProvider.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export async function ClerkProvider<TUi extends Ui = Ui>(
3636
// When dynamic mode is enabled, render scripts in a Suspense boundary to isolate
3737
// the nonce fetching (which calls headers()) from the rest of the page.
3838
// This allows the page to remain statically renderable / use PPR.
39-
const dynamicScripts = dynamic ? (
39+
const scriptsSlot = dynamic ? (
4040
<Suspense>
4141
<DynamicClerkScripts
4242
publishableKey={propsWithEnvs.publishableKey}
@@ -48,16 +48,15 @@ export async function ClerkProvider<TUi extends Ui = Ui>(
4848
prefetchUI={propsWithEnvs.prefetchUI}
4949
/>
5050
</Suspense>
51-
) : null;
51+
) : undefined;
5252

5353
if (shouldRunAsKeyless) {
5454
return (
5555
<KeylessProvider
5656
rest={propsWithEnvs}
5757
runningWithClaimedKeys={runningWithClaimedKeys}
58-
__internal_skipScripts={dynamic}
58+
__internal_scriptsSlot={scriptsSlot}
5959
>
60-
{dynamicScripts}
6160
{children}
6261
</KeylessProvider>
6362
);
@@ -66,9 +65,8 @@ export async function ClerkProvider<TUi extends Ui = Ui>(
6665
return (
6766
<ClientClerkProvider
6867
{...propsWithEnvs}
69-
__internal_skipScripts={dynamic}
68+
__internal_scriptsSlot={scriptsSlot}
7069
>
71-
{dynamicScripts}
7270
{children}
7371
</ClientClerkProvider>
7472
);

packages/nextjs/src/app-router/server/keyless-provider.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ export async function getKeylessStatus(
3232
type KeylessProviderProps = PropsWithChildren<{
3333
rest: Without<NextClerkProviderProps, '__internal_invokeMiddlewareOnAuthStateChange' | 'children'>;
3434
runningWithClaimedKeys: boolean;
35-
__internal_skipScripts?: boolean;
35+
__internal_scriptsSlot?: React.ReactNode;
3636
}>;
3737

3838
export const KeylessProvider = async (props: KeylessProviderProps) => {
39-
const { rest, runningWithClaimedKeys, __internal_skipScripts, children } = props;
39+
const { rest, runningWithClaimedKeys, __internal_scriptsSlot, children } = props;
4040

4141
// NOTE: Create or read keys on every render. Usually this means only on hard refresh or hard navigations.
4242
const newOrReadKeys = await import('../../server/keyless-node.js')
@@ -53,7 +53,7 @@ export const KeylessProvider = async (props: KeylessProviderProps) => {
5353
<ClientClerkProvider
5454
{...mergeNextClerkPropsWithEnv(rest)}
5555
disableKeyless
56-
__internal_skipScripts={__internal_skipScripts}
56+
__internal_scriptsSlot={__internal_scriptsSlot}
5757
>
5858
{children}
5959
</ClientClerkProvider>
@@ -70,7 +70,7 @@ export const KeylessProvider = async (props: KeylessProviderProps) => {
7070
// Explicitly use `null` instead of `undefined` here to avoid persisting `deleteKeylessAction` during merging of options.
7171
__internal_keyless_dismissPrompt: runningWithClaimedKeys ? deleteKeylessAction : null,
7272
})}
73-
__internal_skipScripts={__internal_skipScripts}
73+
__internal_scriptsSlot={__internal_scriptsSlot}
7474
>
7575
{children}
7676
</ClientClerkProvider>

packages/nextjs/src/types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ClerkProviderProps } from '@clerk/react';
22
import type { Ui } from '@clerk/react/internal';
33
import type { Without } from '@clerk/shared/types';
4+
import type React from 'react';
45

56
export type NextClerkProviderProps<TUi extends Ui = Ui> = Without<ClerkProviderProps<TUi>, 'publishableKey'> & {
67
/**
@@ -25,8 +26,8 @@ export type NextClerkProviderProps<TUi extends Ui = Ui> = Without<ClerkProviderP
2526
dynamic?: boolean;
2627
/**
2728
* @internal
28-
* If set to true, the client ClerkProvider will not render ClerkScripts.
29-
* Used when scripts are rendered server-side in a Suspense boundary.
29+
* When provided, the client ClerkProvider will render this slot instead of the default ClerkScripts.
30+
* Used by the server provider to pass scripts rendered in a Suspense boundary.
3031
*/
31-
__internal_skipScripts?: boolean;
32+
__internal_scriptsSlot?: React.ReactNode;
3233
};

0 commit comments

Comments
 (0)