Skip to content

Commit 7b826f6

Browse files
authored
fix(nextjs): Use preload for shared UI variant to avoid race condition (#7685)
1 parent cc799e6 commit 7b826f6

File tree

6 files changed

+77
-6
lines changed

6 files changed

+77
-6
lines changed

.changeset/light-drinks-grow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/nextjs': patch
3+
---
4+
5+
Fix race condition that could cause `__clerkSharedModules is not defined` error when using the shared React UI variant.

integration/presets/envs.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ const withEmailCodes_destroy_client = withEmailCodes
5555
.clone()
5656
.setEnvVariable('public', 'EXPERIMENTAL_PERSIST_CLIENT', 'false');
5757

58+
const withSharedUIVariant = withEmailCodes
59+
.clone()
60+
.setId('withSharedUIVariant')
61+
.setEnvVariable('public', 'CLERK_UI_VARIANT', 'shared');
62+
5863
const withEmailLinks = base
5964
.clone()
6065
.setId('withEmailLinks')
@@ -210,6 +215,7 @@ export const envs = {
210215
withReverification,
211216
withSessionTasks,
212217
withSessionTasksResetPassword,
218+
withSharedUIVariant,
213219
withSignInOrUpEmailLinksFlow,
214220
withSignInOrUpFlow,
215221
withSignInOrUpwithRestrictedModeFlow,

integration/presets/longRunningApps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const createLongRunningApps = () => {
3333
{ id: 'next.appRouter.withSessionTasksResetPassword', config: next.appRouter, env: envs.withSessionTasksResetPassword },
3434
{ id: 'next.appRouter.withLegalConsent', config: next.appRouter, env: envs.withLegalConsent },
3535
{ id: 'next.appRouter.withNeedsClientTrust', config: next.appRouter, env: envs.withNeedsClientTrust },
36+
{ id: 'next.appRouter.withSharedUIVariant', config: next.appRouter, env: envs.withSharedUIVariant },
3637

3738
/**
3839
* Quickstart apps
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { appConfigs } from '../presets';
4+
import { createTestUtils, testAgainstRunningApps } from '../testUtils';
5+
6+
testAgainstRunningApps({ withEnv: [appConfigs.envs.withSharedUIVariant] })(
7+
'shared React variant @nextjs',
8+
({ app }) => {
9+
test.describe.configure({ mode: 'parallel' });
10+
11+
test('loads without __clerkSharedModules error when using shared UI variant', async ({ page, context }) => {
12+
const u = createTestUtils({ app, page, context });
13+
const errors: string[] = [];
14+
15+
page.on('console', msg => {
16+
if (msg.type() === 'error') {
17+
errors.push(msg.text());
18+
}
19+
});
20+
21+
page.on('pageerror', error => {
22+
errors.push(error.message);
23+
});
24+
25+
await page.route('**/ui.browser.js', async route => {
26+
const url = route.request().url().replace('ui.browser.js', 'ui.shared.browser.js');
27+
const response = await page.request.fetch(url);
28+
await route.fulfill({ response });
29+
});
30+
31+
await page.route('**/_next/static/**/*.js', async route => {
32+
await new Promise(resolve => setTimeout(resolve, 300));
33+
await route.continue();
34+
});
35+
36+
await u.page.goToRelative('/clerk-status');
37+
38+
await expect(page.getByText('Status: ready')).toBeVisible({ timeout: 30_000 });
39+
await u.po.clerk.toBeLoaded();
40+
41+
const sharedModulesError = errors.find(e => e.includes('__clerkSharedModules'));
42+
expect(sharedModulesError).toBeUndefined();
43+
});
44+
},
45+
);

packages/nextjs/src/utils/clerk-script.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] })
6767
clerkUIVariant: clerkUIVariant ?? DEFAULT_CLERK_UI_VARIANT,
6868
};
6969

70+
const uiScriptUrl = clerkUiScriptUrl(opts);
71+
const isSharedVariant = opts.clerkUIVariant === 'shared';
72+
7073
return (
7174
<>
7275
<ClerkScript
@@ -75,12 +78,22 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] })
7578
dataAttribute='data-clerk-js-script'
7679
router={router}
7780
/>
78-
<ClerkScript
79-
scriptUrl={clerkUiScriptUrl(opts)}
80-
attributes={buildClerkUiScriptAttributes(opts)}
81-
dataAttribute='data-clerk-ui-script'
82-
router={router}
83-
/>
81+
{isSharedVariant ? (
82+
<link
83+
rel='preload'
84+
href={uiScriptUrl}
85+
as='script'
86+
crossOrigin='anonymous'
87+
nonce={nonce}
88+
/>
89+
) : (
90+
<ClerkScript
91+
scriptUrl={uiScriptUrl}
92+
attributes={buildClerkUiScriptAttributes(opts)}
93+
dataAttribute='data-clerk-ui-script'
94+
router={router}
95+
/>
96+
)}
8497
</>
8598
);
8699
}

packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const mergeNextClerkPropsWithEnv = (props: Omit<NextClerkProviderProps, '
1111
clerkJSUrl: props.clerkJSUrl || process.env.NEXT_PUBLIC_CLERK_JS_URL,
1212
clerkUiUrl: (props as any).clerkUiUrl || process.env.NEXT_PUBLIC_CLERK_UI_URL,
1313
clerkJSVersion: props.clerkJSVersion || process.env.NEXT_PUBLIC_CLERK_JS_VERSION,
14+
clerkUIVariant: (props as any).clerkUIVariant || process.env.NEXT_PUBLIC_CLERK_UI_VARIANT,
1415
proxyUrl: props.proxyUrl || process.env.NEXT_PUBLIC_CLERK_PROXY_URL || '',
1516
domain: props.domain || process.env.NEXT_PUBLIC_CLERK_DOMAIN || '',
1617
isSatellite: props.isSatellite || isTruthy(process.env.NEXT_PUBLIC_CLERK_IS_SATELLITE),

0 commit comments

Comments
 (0)