11import type { Ui } from '@clerk/react/internal' ;
22import type { InitialState , Without } from '@clerk/shared/types' ;
3- import { headers } from 'next/headers' ;
4- import React from 'react' ;
3+ import React , { Suspense } from 'react' ;
54
65import { getDynamicAuthData } from '../../server/buildClerkProps' ;
76import type { NextClerkProviderProps } from '../../types' ;
87import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv' ;
98import { ClientClerkProvider } from '../client/ClerkProvider' ;
9+ import { DynamicClerkScripts } from './DynamicClerkScripts' ;
1010import { getKeylessStatus , KeylessProvider } from './keyless-provider' ;
11- import { buildRequestLike , getScriptNonceFromHeader } from './utils' ;
11+ import { buildRequestLike } from './utils' ;
1212
1313const getDynamicClerkState = React . cache ( async function getDynamicClerkState ( ) {
1414 const request = await buildRequestLike ( ) ;
@@ -17,43 +17,57 @@ const getDynamicClerkState = React.cache(async function getDynamicClerkState() {
1717 return data ;
1818} ) ;
1919
20- const getNonceHeaders = React . cache ( async function getNonceHeaders ( ) {
21- const headersList = await headers ( ) ;
22- const nonce = headersList . get ( 'X-Nonce' ) ;
23- return nonce
24- ? nonce
25- : // Fallback to extracting from CSP header
26- getScriptNonceFromHeader ( headersList . get ( 'Content-Security-Policy' ) || '' ) || '' ;
27- } ) ;
28-
2920export async function ClerkProvider < TUi extends Ui = Ui > (
3021 props : Without < NextClerkProviderProps < TUi > , '__internal_invokeMiddlewareOnAuthStateChange' > ,
3122) {
3223 const { children, dynamic, ...rest } = props ;
3324
3425 const statePromiseOrValue = dynamic ? getDynamicClerkState ( ) : undefined ;
35- const noncePromiseOrValue = dynamic ? getNonceHeaders ( ) : '' ;
3626
3727 const propsWithEnvs = mergeNextClerkPropsWithEnv ( {
3828 ...rest ,
3929 // Even though we always cast to InitialState here, this might still be a promise.
4030 // While not reflected in the public types, we do support this for React >= 19 for internal use.
4131 initialState : statePromiseOrValue as InitialState | undefined ,
42- nonce : await noncePromiseOrValue ,
4332 } ) ;
4433
4534 const { shouldRunAsKeyless, runningWithClaimedKeys } = await getKeylessStatus ( propsWithEnvs ) ;
4635
36+ // When dynamic mode is enabled, render scripts in a Suspense boundary to isolate
37+ // the nonce fetching (which calls headers()) from the rest of the page.
38+ // This allows the page to remain statically renderable / use PPR.
39+ const scriptsSlot = dynamic ? (
40+ < Suspense >
41+ < DynamicClerkScripts
42+ publishableKey = { propsWithEnvs . publishableKey }
43+ clerkJSUrl = { propsWithEnvs . clerkJSUrl }
44+ clerkJSVersion = { propsWithEnvs . clerkJSVersion }
45+ clerkUIUrl = { propsWithEnvs . clerkUIUrl }
46+ domain = { propsWithEnvs . domain }
47+ proxyUrl = { propsWithEnvs . proxyUrl }
48+ prefetchUI = { propsWithEnvs . prefetchUI }
49+ />
50+ </ Suspense >
51+ ) : undefined ;
52+
4753 if ( shouldRunAsKeyless ) {
4854 return (
4955 < KeylessProvider
5056 rest = { propsWithEnvs }
5157 runningWithClaimedKeys = { runningWithClaimedKeys }
58+ __internal_scriptsSlot = { scriptsSlot }
5259 >
5360 { children }
5461 </ KeylessProvider >
5562 ) ;
5663 }
5764
58- return < ClientClerkProvider { ...propsWithEnvs } > { children } </ ClientClerkProvider > ;
65+ return (
66+ < ClientClerkProvider
67+ { ...propsWithEnvs }
68+ __internal_scriptsSlot = { scriptsSlot }
69+ >
70+ { children }
71+ </ ClientClerkProvider >
72+ ) ;
5973}
0 commit comments