Skip to content

Commit 4ca18e2

Browse files
committed
refactor(core): use dynamic imports in client to avoid AsyncLocalStorage poisoning
Replace static imports of next/headers, next/navigation, and next-intl/server with dynamic import() calls inside the hooks that use them. Static imports cause these modules to be evaluated during module graph resolution when next.config.ts imports this file, poisoning the process-wide AsyncLocalStorage context. Dynamic imports defer module loading to call time, after Next.js has fully initialized.
1 parent 8b5fee6 commit 4ca18e2

1 file changed

Lines changed: 18 additions & 7 deletions

File tree

core/client/index.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
import { BigCommerceAuthError, createClient } from '@bigcommerce/catalyst-client';
2-
import { headers } from 'next/headers';
3-
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
4-
import { redirect } from 'next/navigation';
5-
import { getLocale as getServerLocale } from 'next-intl/server';
62

73
import { getChannelIdFromLocale } from '../channels.config';
84
import { backendUserAgent } from '../user-agent';
95

6+
// next/headers, next/navigation, and next-intl/server are imported dynamically
7+
// (via `import()`) rather than statically. Static imports cause these modules to
8+
// be evaluated during module graph resolution when next.config.ts imports this
9+
// file, which poisons the process-wide AsyncLocalStorage context (pnpm symlinks
10+
// create two separate singleton instances of next/headers). Dynamic imports
11+
// defer module loading to call time, after Next.js has fully initialized.
12+
//
13+
// During config resolution, the dynamic import of next-intl/server succeeds but
14+
// getLocale() throws ("not supported in Client Components") — the try/catch
15+
// below absorbs this gracefully, and getChannelId falls back to defaultChannelId.
16+
1017
const getLocale = async () => {
1118
try {
12-
const locale = await getServerLocale();
19+
const { getLocale: getServerLocale } = await import('next-intl/server');
1320

14-
return locale;
21+
return await getServerLocale();
1522
} catch {
1623
/**
1724
* Next-intl `getLocale` only works on the server, and when middleware has run.
1825
*
1926
* Instances when `getLocale` will not work:
27+
* - Requests during next.config.ts resolution
2028
* - Requests in middlewares
2129
* - Requests in `generateStaticParams`
2230
* - Request in api routes
@@ -45,6 +53,7 @@ export const client = createClient({
4553
const locale = await getLocale();
4654

4755
if (fetchOptions?.cache && ['no-store', 'no-cache'].includes(fetchOptions.cache)) {
56+
const { headers } = await import('next/headers');
4857
const ipAddress = (await headers()).get('X-Forwarded-For');
4958

5059
if (ipAddress) {
@@ -61,8 +70,10 @@ export const client = createClient({
6170
headers: requestHeaders,
6271
};
6372
},
64-
onError: (error, queryType) => {
73+
onError: async (error, queryType) => {
6574
if (error instanceof BigCommerceAuthError && queryType === 'query') {
75+
const { redirect } = await import('next/navigation');
76+
6677
redirect('/api/auth/signout');
6778
}
6879
},

0 commit comments

Comments
 (0)