1
1
import { useClerk } from '@clerk/clerk-react' ;
2
2
import { buildClerkJsScriptAttributes , clerkJsScriptUrl } from '@clerk/clerk-react/internal' ;
3
+ import Head from 'next/head' ;
3
4
import NextScript from 'next/script' ;
4
5
import React , { useCallback , useEffect , useRef , useState } from 'react' ;
5
6
@@ -149,17 +150,14 @@ function useClerkJSLoadingState() {
149
150
/**
150
151
* Enhanced ClerkJS Script component with bulletproof load detection.
151
152
*
152
- * This component renders TWO script tags:
153
- * 1. A render-blocking inline script that sets up the coordinator globally
154
- * 2. The actual ClerkJS script tag (which the coordinator will manage)
155
- *
156
- * The coordinator uses comprehensive DOM interception to catch scripts regardless
157
- * of where they're placed (head, body, or injected dynamically).
153
+ * This component ensures the blocking coordinator is loaded in the document head
154
+ * before any ClerkJS scripts, regardless of the router type.
158
155
*/
159
156
function ClerkJSScript ( props : ClerkJSScriptProps ) {
160
157
const { publishableKey, clerkJSUrl, clerkJSVersion, clerkJSVariant, nonce } = useClerkNextOptions ( ) ;
161
158
const { domain, proxyUrl } = useClerk ( ) ;
162
159
const scriptRef = useRef < HTMLScriptElement > ( null ) ;
160
+ const coordinatorInjected = useRef ( false ) ;
163
161
164
162
/**
165
163
* If no publishable key, avoid appending invalid scripts in the DOM.
@@ -179,6 +177,31 @@ function ClerkJSScript(props: ClerkJSScriptProps) {
179
177
} ;
180
178
const scriptUrl = clerkJsScriptUrl ( options ) ;
181
179
180
+ // Inject coordinator script into head manually to ensure it's there first
181
+ useEffect ( ( ) => {
182
+ if ( typeof window === 'undefined' || coordinatorInjected . current ) return ;
183
+
184
+ // Check if coordinator already exists
185
+ if ( ( window as any ) . __clerkJSBlockingCoordinator ) {
186
+ coordinatorInjected . current = true ;
187
+ return ;
188
+ }
189
+
190
+ // Create and inject coordinator script into head
191
+ const coordinatorScript = document . createElement ( 'script' ) ;
192
+ coordinatorScript . id = 'clerk-blocking-coordinator' ;
193
+ coordinatorScript . innerHTML = getBlockingCoordinatorScript ( ) ;
194
+
195
+ // Insert at the beginning of head to ensure it runs first
196
+ if ( document . head . firstChild ) {
197
+ document . head . insertBefore ( coordinatorScript , document . head . firstChild ) ;
198
+ } else {
199
+ document . head . appendChild ( coordinatorScript ) ;
200
+ }
201
+
202
+ coordinatorInjected . current = true ;
203
+ } , [ ] ) ;
204
+
182
205
// Handle state changes from the blocking coordinator
183
206
const handleLoad = useCallback ( ( ) => {
184
207
props . onLoad ?.( ) ;
@@ -210,17 +233,9 @@ function ClerkJSScript(props: ClerkJSScriptProps) {
210
233
const scriptAttributes = buildClerkJsScriptAttributes ( options ) ;
211
234
212
235
if ( props . router === 'app' ) {
213
- // For App Router, use regular script tags
214
- // The coordinator will catch these regardless of placement
236
+ // For App Router, use Next.js Head component to ensure script goes to head
215
237
return (
216
- < >
217
- { /* Blocking coordinator script - MUST run first */ }
218
- < script
219
- dangerouslySetInnerHTML = { { __html : getBlockingCoordinatorScript ( ) } }
220
- // No async/defer - this must block to set up coordination
221
- />
222
-
223
- { /* Actual ClerkJS script - managed by the coordinator */ }
238
+ < Head >
224
239
< script
225
240
ref = { scriptRef }
226
241
src = { scriptUrl }
@@ -229,31 +244,20 @@ function ClerkJSScript(props: ClerkJSScriptProps) {
229
244
crossOrigin = 'anonymous'
230
245
{ ...scriptAttributes }
231
246
/>
232
- </ >
247
+ </ Head >
233
248
) ;
234
249
} else {
235
250
// For Pages Router, use Next.js Script components with beforeInteractive
236
- // This ensures both scripts are placed in head and execute early
237
251
return (
238
- < >
239
- { /* Blocking coordinator script - MUST run first and block */ }
240
- < NextScript
241
- id = 'clerk-blocking-coordinator'
242
- strategy = 'beforeInteractive'
243
- dangerouslySetInnerHTML = { { __html : getBlockingCoordinatorScript ( ) } }
244
- />
245
-
246
- { /* Actual ClerkJS script - managed by the coordinator */ }
247
- < NextScript
248
- src = { scriptUrl }
249
- data-clerk-js-script = 'true'
250
- async
251
- defer = { false }
252
- crossOrigin = 'anonymous'
253
- strategy = 'beforeInteractive'
254
- { ...scriptAttributes }
255
- />
256
- </ >
252
+ < NextScript
253
+ src = { scriptUrl }
254
+ data-clerk-js-script = 'true'
255
+ async
256
+ defer = { false }
257
+ crossOrigin = 'anonymous'
258
+ strategy = 'beforeInteractive'
259
+ { ...scriptAttributes }
260
+ />
257
261
) ;
258
262
}
259
263
}
0 commit comments