From 75f296599274092d978cbb6d2e586de6ea501ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 16 Feb 2024 13:14:05 +0100 Subject: [PATCH 01/16] fix: clarify `next/headers` function calls in wrong context --- .../components/request-async-storage.external.ts | 11 +++++------ .../static-generation-async-storage.external.ts | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index 147764db04b66..2303e09d8280f 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -20,10 +20,9 @@ export const requestAsyncStorage: RequestAsyncStorage = export function getExpectedRequestStore(callingExpression: string) { const store = requestAsyncStorage.getStore() - if (!store) { - throw new Error( - `Invariant: \`${callingExpression}\` expects to have requestAsyncStorage, none available.` - ) - } - return store + if (store) return store + throw new Error( + `\`${callingExpression}()\` was called outside a request/render scope. +Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` + ) } diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index ace72ad9d9bae..5097a891d1236 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -58,10 +58,9 @@ export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = export function getExpectedStaticGenerationStore(callingExpression: string) { const store = staticGenerationAsyncStorage.getStore() - if (!store) { - throw new Error( - `Invariant: \`${callingExpression}\` expects to have staticGenerationAsyncStorage, none available.` - ) - } - return store + if (store) return store + throw new Error( + `\`${callingExpression}()\` was called outside a request/render scope. +Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` + ) } From f624d8a90a84e84b4beb5ef272a70cc9819aa9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 16 Feb 2024 13:14:16 +0100 Subject: [PATCH 02/16] docs: add corresponding docs page --- errors/next-headers-wrong-context.mdx | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 errors/next-headers-wrong-context.mdx diff --git a/errors/next-headers-wrong-context.mdx b/errors/next-headers-wrong-context.mdx new file mode 100644 index 0000000000000..832531a1746c5 --- /dev/null +++ b/errors/next-headers-wrong-context.mdx @@ -0,0 +1,44 @@ +--- +title: `next/headers` methods were called outside request/render scope +--- + +## `` + +#### Why This Error Occurred + +Either `headers()` or `cookies()` were invokned at place where no request information was available (eg.: Global scope, outside the React lifecycle). + +Note that `headers()` and `cookies()` could be called deep inside other modules/functions (eg.: third-party libraries) that are not immediately visible. + +#### Possible Ways to Fix It + +Make sure that all `headers()` and `cookies()` calls happen in a request/render scope. + +Example: + +```diff +// app/page.ts +import { cookies } from 'next/headers' + +- const cookieStore = cookies() +export default function Page() { ++ const cookieStore = cookies() + return ... +} +``` + +```diff +// app/foo/route.ts +import { headers } from 'next/headers' + +- const headersList = headers() +export async function GET() { ++ const headersList = headers() + return ... +} +``` + +### Useful Links + +- [`headers()` function](https://nextjs.org/docs/app/api-reference/functions/headers) +- [`cookies()` function](https://nextjs.org/docs/app/api-reference/functions/cookies) From f9d290e19c2f7c0e0b83ccfddc7776095f2f2be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 16 Feb 2024 13:21:24 +0100 Subject: [PATCH 03/16] fix docs title --- errors/next-headers-wrong-context.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors/next-headers-wrong-context.mdx b/errors/next-headers-wrong-context.mdx index 832531a1746c5..95bd6faa3a69c 100644 --- a/errors/next-headers-wrong-context.mdx +++ b/errors/next-headers-wrong-context.mdx @@ -1,5 +1,5 @@ --- -title: `next/headers` methods were called outside request/render scope +title: next/headers methods were called outside request/render scope --- ## `` From 5acfc12ef995f7c19f607818eec3030aeddc474e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 16 Feb 2024 13:22:36 +0100 Subject: [PATCH 04/16] drop new line --- .../src/client/components/request-async-storage.external.ts | 3 +-- .../components/static-generation-async-storage.external.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index 2303e09d8280f..ee74681e4d103 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -22,7 +22,6 @@ export function getExpectedRequestStore(callingExpression: string) { const store = requestAsyncStorage.getStore() if (store) return store throw new Error( - `\`${callingExpression}()\` was called outside a request/render scope. -Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` + `\`${callingExpression}()\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` ) } diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index 5097a891d1236..a4befec6416fa 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -60,7 +60,6 @@ export function getExpectedStaticGenerationStore(callingExpression: string) { const store = staticGenerationAsyncStorage.getStore() if (store) return store throw new Error( - `\`${callingExpression}()\` was called outside a request/render scope. -Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` + `\`${callingExpression}()\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` ) } From b2378b5ca572fd3924c28353be991e6c5defcaef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 16 Feb 2024 15:32:57 +0100 Subject: [PATCH 05/16] fix test --- test/development/acceptance-app/rsc-runtime-errors.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/development/acceptance-app/rsc-runtime-errors.test.ts b/test/development/acceptance-app/rsc-runtime-errors.test.ts index 5847497cead14..32f7aa82b49b7 100644 --- a/test/development/acceptance-app/rsc-runtime-errors.test.ts +++ b/test/development/acceptance-app/rsc-runtime-errors.test.ts @@ -74,7 +74,7 @@ createNextDescribe( const errorDescription = await getRedboxDescription(browser) expect(errorDescription).toContain( - `Error: Invariant: \`cookies\` expects to have requestAsyncStorage, none available.` + 'Error: `cookies()` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-headers-wrong-context' ) }) From 49dfe25d267eba7cfe7b1a8e57f1460d656cbc2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Fri, 16 Feb 2024 15:41:19 +0100 Subject: [PATCH 06/16] Update next-headers-wrong-context.mdx --- errors/next-headers-wrong-context.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/errors/next-headers-wrong-context.mdx b/errors/next-headers-wrong-context.mdx index 95bd6faa3a69c..dd2fb38b1463c 100644 --- a/errors/next-headers-wrong-context.mdx +++ b/errors/next-headers-wrong-context.mdx @@ -2,8 +2,6 @@ title: next/headers methods were called outside request/render scope --- -## `` - #### Why This Error Occurred Either `headers()` or `cookies()` were invokned at place where no request information was available (eg.: Global scope, outside the React lifecycle). From 50304c039a02b50b5304ac39316a4361ac157e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 00:46:29 +0100 Subject: [PATCH 07/16] rename docs --- errors/next-dynamic-api-wrong-context.mdx | 48 +++++++++++++++++++++++ errors/next-headers-wrong-context.mdx | 44 --------------------- 2 files changed, 48 insertions(+), 44 deletions(-) create mode 100644 errors/next-dynamic-api-wrong-context.mdx delete mode 100644 errors/next-headers-wrong-context.mdx diff --git a/errors/next-dynamic-api-wrong-context.mdx b/errors/next-dynamic-api-wrong-context.mdx new file mode 100644 index 0000000000000..ed75c3031a2e7 --- /dev/null +++ b/errors/next-dynamic-api-wrong-context.mdx @@ -0,0 +1,48 @@ +--- +title: Dynamic API was called outside request/render scope +--- + +## `` + +#### Why This Error Occurred + +A Dynamic API was called outside of a request/render scope, I.e. not inside a Route Handler or a React Server Component/Server Action. +(The global scope is the most common place where these APIs won't work). + +Note that Dynamic APIs could have been called deep inside other modules/functions (eg.: third-party libraries) that are not immediately visible. + +#### Possible Ways to Fix It + +Make sure that all Dynamic API calls happen in a request/render scope. + +Example: + +```diff +// app/page.ts +import { cookies } from 'next/headers' + +- const cookieStore = cookies() +export default function Page() { ++ const cookieStore = cookies() + return ... +} +``` + +```diff +// app/foo/route.ts +import { headers } from 'next/headers' + +- const headersList = headers() +export async function GET() { ++ const headersList = headers() + return ... +} +``` + +### Useful Links + +- [`headers()` function](https://nextjs.org/docs/app/api-reference/functions/headers) +- [`cookies()` function](https://nextjs.org/docs/app/api-reference/functions/cookies) +- [`draftMode()` function](https://nextjs.org/docs/app/api-reference/functions/draft-mode) +- [`unstable_noStore()` function](https://nextjs.org/docs/app/api-reference/functions/unstable_noStore) +- [`unstable_cache()` function](https://nextjs.org/docs/app/api-reference/functions/unstable_cache) diff --git a/errors/next-headers-wrong-context.mdx b/errors/next-headers-wrong-context.mdx deleted file mode 100644 index 95bd6faa3a69c..0000000000000 --- a/errors/next-headers-wrong-context.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: next/headers methods were called outside request/render scope ---- - -## `` - -#### Why This Error Occurred - -Either `headers()` or `cookies()` were invokned at place where no request information was available (eg.: Global scope, outside the React lifecycle). - -Note that `headers()` and `cookies()` could be called deep inside other modules/functions (eg.: third-party libraries) that are not immediately visible. - -#### Possible Ways to Fix It - -Make sure that all `headers()` and `cookies()` calls happen in a request/render scope. - -Example: - -```diff -// app/page.ts -import { cookies } from 'next/headers' - -- const cookieStore = cookies() -export default function Page() { -+ const cookieStore = cookies() - return ... -} -``` - -```diff -// app/foo/route.ts -import { headers } from 'next/headers' - -- const headersList = headers() -export async function GET() { -+ const headersList = headers() - return ... -} -``` - -### Useful Links - -- [`headers()` function](https://nextjs.org/docs/app/api-reference/functions/headers) -- [`cookies()` function](https://nextjs.org/docs/app/api-reference/functions/cookies) From 1c9dff4147f6d2cac2b832007fc64a2757e1deb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 00:47:58 +0100 Subject: [PATCH 08/16] cleanup, fix test --- .../next/src/client/components/headers.ts | 22 +++++++++---------- .../request-async-storage.external.ts | 2 +- ...tatic-generation-async-storage.external.ts | 8 +++++-- .../web/spec-extension/unstable-cache.ts | 9 ++++---- .../web/spec-extension/unstable-no-store.ts | 22 ++++++++----------- .../acceptance-app/rsc-runtime-errors.test.ts | 2 +- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/next/src/client/components/headers.ts b/packages/next/src/client/components/headers.ts index abd0780734542..89118919e04b9 100644 --- a/packages/next/src/client/components/headers.ts +++ b/packages/next/src/client/components/headers.ts @@ -7,7 +7,7 @@ import { RequestCookies } from '../../server/web/spec-extension/cookies' import { actionAsyncStorage } from './action-async-storage.external' import { DraftMode } from './draft-mode' import { trackDynamicDataAccessed } from '../../server/app-render/dynamic-rendering' -import { staticGenerationAsyncStorage } from './static-generation-async-storage.external' +import { getExpectedStaticGenerationStore } from './static-generation-async-storage.external' import { getExpectedRequestStore } from './request-async-storage.external' /** @@ -20,8 +20,9 @@ import { getExpectedRequestStore } from './request-async-storage.external' * Read more: [Next.js Docs: `headers`](https://nextjs.org/docs/app/api-reference/functions/headers) */ export function headers() { - const callingExpression = 'headers' - const staticGenerationStore = staticGenerationAsyncStorage.getStore() + const callingExpression = 'headers()' + const staticGenerationStore = + getExpectedStaticGenerationStore(callingExpression) if (staticGenerationStore) { if (staticGenerationStore.forceStatic) { @@ -33,13 +34,13 @@ export function headers() { } } - const requestStore = getExpectedRequestStore(callingExpression) - return requestStore.headers + return getExpectedRequestStore(callingExpression).headers } export function cookies() { - const callingExpression = 'cookies' - const staticGenerationStore = staticGenerationAsyncStorage.getStore() + const callingExpression = 'cookies()' + const staticGenerationStore = + getExpectedStaticGenerationStore(callingExpression) if (staticGenerationStore) { if (staticGenerationStore.forceStatic) { @@ -54,10 +55,7 @@ export function cookies() { const requestStore = getExpectedRequestStore(callingExpression) const asyncActionStore = actionAsyncStorage.getStore() - if ( - asyncActionStore && - (asyncActionStore.isAction || asyncActionStore.isAppRoute) - ) { + if (asyncActionStore?.isAction || asyncActionStore?.isAppRoute) { // We can't conditionally return different types here based on the context. // To avoid confusion, we always return the readonly type here. return requestStore.mutableCookies as unknown as ReadonlyRequestCookies @@ -67,7 +65,7 @@ export function cookies() { } export function draftMode() { - const callingExpression = 'draftMode' + const callingExpression = 'draftMode()' const requestStore = getExpectedRequestStore(callingExpression) return new DraftMode(requestStore.draftMode) diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index ee74681e4d103..5ee0e45570832 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -22,6 +22,6 @@ export function getExpectedRequestStore(callingExpression: string) { const store = requestAsyncStorage.getStore() if (store) return store throw new Error( - `\`${callingExpression}()\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` + `\`${callingExpression}\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` ) } diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index a4befec6416fa..9eab6cbb81a11 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -56,10 +56,14 @@ export type StaticGenerationAsyncStorage = export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = createAsyncLocalStorage() -export function getExpectedStaticGenerationStore(callingExpression: string) { +export function getExpectedStaticGenerationStore( + callingExpression: string, + shouldThrowIfMissing = true +) { const store = staticGenerationAsyncStorage.getStore() if (store) return store + if (!shouldThrowIfMissing) return throw new Error( - `\`${callingExpression}()\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-headers-wrong-context` + `\`${callingExpression}\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` ) } diff --git a/packages/next/src/server/web/spec-extension/unstable-cache.ts b/packages/next/src/server/web/spec-extension/unstable-cache.ts index 153da4634b453..e2a4e32a102b0 100644 --- a/packages/next/src/server/web/spec-extension/unstable-cache.ts +++ b/packages/next/src/server/web/spec-extension/unstable-cache.ts @@ -62,8 +62,10 @@ export function unstable_cache( tags?: string[] } = {} ): T { - const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = - (fetch as any).__nextGetStaticStore?.() || _staticGenerationAsyncStorage + const staticGenerationAsyncStorage = + ((fetch as any).__nextGetStaticStore?.() as + | StaticGenerationAsyncStorage + | undefined) ?? _staticGenerationAsyncStorage if (options.revalidate === 0) { throw new Error( @@ -94,8 +96,7 @@ export function unstable_cache( }` const cachedCb = async (...args: any[]) => { - const store: undefined | StaticGenerationStore = - staticGenerationAsyncStorage?.getStore() + const store = staticGenerationAsyncStorage.getStore() // We must be able to find the incremental cache otherwise we throw const maybeIncrementalCache: diff --git a/packages/next/src/server/web/spec-extension/unstable-no-store.ts b/packages/next/src/server/web/spec-extension/unstable-no-store.ts index 4999b6ed01413..e499431daf595 100644 --- a/packages/next/src/server/web/spec-extension/unstable-no-store.ts +++ b/packages/next/src/server/web/spec-extension/unstable-no-store.ts @@ -1,4 +1,4 @@ -import { staticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external' +import { getExpectedStaticGenerationStore } from '../../../client/components/static-generation-async-storage.external' import { markCurrentScopeAsDynamic } from '../../app-render/dynamic-rendering' /** @@ -18,16 +18,12 @@ import { markCurrentScopeAsDynamic } from '../../app-render/dynamic-rendering' */ export function unstable_noStore() { const callingExpression = 'unstable_noStore()' - const store = staticGenerationAsyncStorage.getStore() - if (!store) { - // This generally implies we are being called in Pages router. We should probably not support - // unstable_noStore in contexts outside of `react-server` condition but since we historically - // have not errored here previously, we maintain that behavior for now. - return - } else if (store.forceStatic) { - return - } else { - store.isUnstableNoStore = true - markCurrentScopeAsDynamic(store, callingExpression) - } + // This generally implies we are being called in Pages Router. We should probably not support + // unstable_noStore in contexts outside of `react-server` condition but since we historically + // have not errored here previously, we maintain that behavior for now. + const store = getExpectedStaticGenerationStore(callingExpression, false) + if (!store) return + else if (store.forceStatic) return + store.isUnstableNoStore = true + markCurrentScopeAsDynamic(store, callingExpression) } diff --git a/test/development/acceptance-app/rsc-runtime-errors.test.ts b/test/development/acceptance-app/rsc-runtime-errors.test.ts index 32f7aa82b49b7..dad7824f32299 100644 --- a/test/development/acceptance-app/rsc-runtime-errors.test.ts +++ b/test/development/acceptance-app/rsc-runtime-errors.test.ts @@ -74,7 +74,7 @@ createNextDescribe( const errorDescription = await getRedboxDescription(browser) expect(errorDescription).toContain( - 'Error: `cookies()` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-headers-wrong-context' + 'Error: `cookies()` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context' ) }) From 0c275f507fb95ae652cd1b2d22be32b110df54c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 00:50:10 +0100 Subject: [PATCH 09/16] Update next-dynamic-api-wrong-context.mdx --- errors/next-dynamic-api-wrong-context.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/errors/next-dynamic-api-wrong-context.mdx b/errors/next-dynamic-api-wrong-context.mdx index ed75c3031a2e7..ab6cf251b60d4 100644 --- a/errors/next-dynamic-api-wrong-context.mdx +++ b/errors/next-dynamic-api-wrong-context.mdx @@ -2,8 +2,6 @@ title: Dynamic API was called outside request/render scope --- -## `` - #### Why This Error Occurred A Dynamic API was called outside of a request/render scope, I.e. not inside a Route Handler or a React Server Component/Server Action. From a900e7109f5022b580718dba34eb4dfff1a5f8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 00:52:49 +0100 Subject: [PATCH 10/16] Update next-dynamic-api-wrong-context.mdx --- errors/next-dynamic-api-wrong-context.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors/next-dynamic-api-wrong-context.mdx b/errors/next-dynamic-api-wrong-context.mdx index ab6cf251b60d4..5daf18f0c3afd 100644 --- a/errors/next-dynamic-api-wrong-context.mdx +++ b/errors/next-dynamic-api-wrong-context.mdx @@ -4,7 +4,7 @@ title: Dynamic API was called outside request/render scope #### Why This Error Occurred -A Dynamic API was called outside of a request/render scope, I.e. not inside a Route Handler or a React Server Component/Server Action. +A Dynamic API was called outside a request/render scope, i.e. not inside a Route Handler or a React Server Component/Server Action. (The global scope is the most common place where these APIs won't work). Note that Dynamic APIs could have been called deep inside other modules/functions (eg.: third-party libraries) that are not immediately visible. From 45f3141f8f7ea93dcccc5e3c7032022108dc9e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 01:04:28 +0100 Subject: [PATCH 11/16] rephrase --- errors/next-dynamic-api-wrong-context.mdx | 7 +++---- .../client/components/request-async-storage.external.ts | 2 +- .../components/static-generation-async-storage.external.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/errors/next-dynamic-api-wrong-context.mdx b/errors/next-dynamic-api-wrong-context.mdx index 5daf18f0c3afd..064baba8828e0 100644 --- a/errors/next-dynamic-api-wrong-context.mdx +++ b/errors/next-dynamic-api-wrong-context.mdx @@ -1,17 +1,16 @@ --- -title: Dynamic API was called outside request/render scope +title: Dynamic API was called outside request --- #### Why This Error Occurred -A Dynamic API was called outside a request/render scope, i.e. not inside a Route Handler or a React Server Component/Server Action. -(The global scope is the most common place where these APIs won't work). +A Dynamic API was called outside a request scope. (Eg.: Global scope). Note that Dynamic APIs could have been called deep inside other modules/functions (eg.: third-party libraries) that are not immediately visible. #### Possible Ways to Fix It -Make sure that all Dynamic API calls happen in a request/render scope. +Make sure that all Dynamic API calls happen in a request scope. Example: diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index 5ee0e45570832..7707485bccb7a 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -22,6 +22,6 @@ export function getExpectedRequestStore(callingExpression: string) { const store = requestAsyncStorage.getStore() if (store) return store throw new Error( - `\`${callingExpression}\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` + `\`${callingExpression}\` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` ) } diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index 9eab6cbb81a11..0e5963f6074a1 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -64,6 +64,6 @@ export function getExpectedStaticGenerationStore( if (store) return store if (!shouldThrowIfMissing) return throw new Error( - `\`${callingExpression}\` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` + `\`${callingExpression}\` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` ) } From 0e206ee1bde243990618fe49e217ec4e69e50a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 01:06:56 +0100 Subject: [PATCH 12/16] fix lint --- .../next/src/server/web/spec-extension/unstable-cache.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/next/src/server/web/spec-extension/unstable-cache.ts b/packages/next/src/server/web/spec-extension/unstable-cache.ts index e2a4e32a102b0..3d6b374dc58ad 100644 --- a/packages/next/src/server/web/spec-extension/unstable-cache.ts +++ b/packages/next/src/server/web/spec-extension/unstable-cache.ts @@ -1,7 +1,4 @@ -import type { - StaticGenerationStore, - StaticGenerationAsyncStorage, -} from '../../../client/components/static-generation-async-storage.external' +import type { StaticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external' import type { IncrementalCache } from '../../lib/incremental-cache' import { staticGenerationAsyncStorage as _staticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external' From ec12eaf49d204e12671d4c7bdaf0fbb32ff8dc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 01:08:14 +0100 Subject: [PATCH 13/16] Update rsc-runtime-errors.test.ts --- test/development/acceptance-app/rsc-runtime-errors.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/development/acceptance-app/rsc-runtime-errors.test.ts b/test/development/acceptance-app/rsc-runtime-errors.test.ts index dad7824f32299..be72fddc33519 100644 --- a/test/development/acceptance-app/rsc-runtime-errors.test.ts +++ b/test/development/acceptance-app/rsc-runtime-errors.test.ts @@ -74,7 +74,7 @@ createNextDescribe( const errorDescription = await getRedboxDescription(browser) expect(errorDescription).toContain( - 'Error: `cookies()` was called outside a request/render scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context' + 'Error: `cookies()` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context' ) }) From 2f6c22f79209985d3a321f7da00a72adeddebdc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 01:10:30 +0100 Subject: [PATCH 14/16] revert/fix --- packages/next/src/client/components/headers.ts | 6 +++--- test/development/acceptance-app/rsc-runtime-errors.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/next/src/client/components/headers.ts b/packages/next/src/client/components/headers.ts index 89118919e04b9..5658866a2a0f0 100644 --- a/packages/next/src/client/components/headers.ts +++ b/packages/next/src/client/components/headers.ts @@ -20,7 +20,7 @@ import { getExpectedRequestStore } from './request-async-storage.external' * Read more: [Next.js Docs: `headers`](https://nextjs.org/docs/app/api-reference/functions/headers) */ export function headers() { - const callingExpression = 'headers()' + const callingExpression = 'headers' const staticGenerationStore = getExpectedStaticGenerationStore(callingExpression) @@ -38,7 +38,7 @@ export function headers() { } export function cookies() { - const callingExpression = 'cookies()' + const callingExpression = 'cookies' const staticGenerationStore = getExpectedStaticGenerationStore(callingExpression) @@ -65,7 +65,7 @@ export function cookies() { } export function draftMode() { - const callingExpression = 'draftMode()' + const callingExpression = 'draftMode' const requestStore = getExpectedRequestStore(callingExpression) return new DraftMode(requestStore.draftMode) diff --git a/test/development/acceptance-app/rsc-runtime-errors.test.ts b/test/development/acceptance-app/rsc-runtime-errors.test.ts index be72fddc33519..8b53ced2ec164 100644 --- a/test/development/acceptance-app/rsc-runtime-errors.test.ts +++ b/test/development/acceptance-app/rsc-runtime-errors.test.ts @@ -74,7 +74,7 @@ createNextDescribe( const errorDescription = await getRedboxDescription(browser) expect(errorDescription).toContain( - 'Error: `cookies()` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context' + 'Error: `cookies` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context' ) }) From d483de46b0d6c113571f46111250ba373a75dabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 01:11:46 +0100 Subject: [PATCH 15/16] Update unstable-no-store.ts --- .../next/src/server/web/spec-extension/unstable-no-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/web/spec-extension/unstable-no-store.ts b/packages/next/src/server/web/spec-extension/unstable-no-store.ts index e499431daf595..ebe7ab6be7ede 100644 --- a/packages/next/src/server/web/spec-extension/unstable-no-store.ts +++ b/packages/next/src/server/web/spec-extension/unstable-no-store.ts @@ -18,10 +18,10 @@ import { markCurrentScopeAsDynamic } from '../../app-render/dynamic-rendering' */ export function unstable_noStore() { const callingExpression = 'unstable_noStore()' + const store = getExpectedStaticGenerationStore(callingExpression, false) // This generally implies we are being called in Pages Router. We should probably not support // unstable_noStore in contexts outside of `react-server` condition but since we historically // have not errored here previously, we maintain that behavior for now. - const store = getExpectedStaticGenerationStore(callingExpression, false) if (!store) return else if (store.forceStatic) return store.isUnstableNoStore = true From a8a1946a0f4451f008f7ae0a9d7ae5681fec4d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Sun, 18 Feb 2024 01:24:53 +0100 Subject: [PATCH 16/16] revert, drop unused function --- .../next/src/client/components/headers.ts | 8 +++---- ...tatic-generation-async-storage.external.ts | 12 ---------- .../web/spec-extension/unstable-no-store.ts | 22 +++++++++++-------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/packages/next/src/client/components/headers.ts b/packages/next/src/client/components/headers.ts index 5658866a2a0f0..38432c01bc31d 100644 --- a/packages/next/src/client/components/headers.ts +++ b/packages/next/src/client/components/headers.ts @@ -7,7 +7,7 @@ import { RequestCookies } from '../../server/web/spec-extension/cookies' import { actionAsyncStorage } from './action-async-storage.external' import { DraftMode } from './draft-mode' import { trackDynamicDataAccessed } from '../../server/app-render/dynamic-rendering' -import { getExpectedStaticGenerationStore } from './static-generation-async-storage.external' +import { staticGenerationAsyncStorage } from './static-generation-async-storage.external' import { getExpectedRequestStore } from './request-async-storage.external' /** @@ -21,8 +21,7 @@ import { getExpectedRequestStore } from './request-async-storage.external' */ export function headers() { const callingExpression = 'headers' - const staticGenerationStore = - getExpectedStaticGenerationStore(callingExpression) + const staticGenerationStore = staticGenerationAsyncStorage.getStore() if (staticGenerationStore) { if (staticGenerationStore.forceStatic) { @@ -39,8 +38,7 @@ export function headers() { export function cookies() { const callingExpression = 'cookies' - const staticGenerationStore = - getExpectedStaticGenerationStore(callingExpression) + const staticGenerationStore = staticGenerationAsyncStorage.getStore() if (staticGenerationStore) { if (staticGenerationStore.forceStatic) { diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index 0e5963f6074a1..4433992a8f331 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -55,15 +55,3 @@ export type StaticGenerationAsyncStorage = export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = createAsyncLocalStorage() - -export function getExpectedStaticGenerationStore( - callingExpression: string, - shouldThrowIfMissing = true -) { - const store = staticGenerationAsyncStorage.getStore() - if (store) return store - if (!shouldThrowIfMissing) return - throw new Error( - `\`${callingExpression}\` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context` - ) -} diff --git a/packages/next/src/server/web/spec-extension/unstable-no-store.ts b/packages/next/src/server/web/spec-extension/unstable-no-store.ts index ebe7ab6be7ede..4999b6ed01413 100644 --- a/packages/next/src/server/web/spec-extension/unstable-no-store.ts +++ b/packages/next/src/server/web/spec-extension/unstable-no-store.ts @@ -1,4 +1,4 @@ -import { getExpectedStaticGenerationStore } from '../../../client/components/static-generation-async-storage.external' +import { staticGenerationAsyncStorage } from '../../../client/components/static-generation-async-storage.external' import { markCurrentScopeAsDynamic } from '../../app-render/dynamic-rendering' /** @@ -18,12 +18,16 @@ import { markCurrentScopeAsDynamic } from '../../app-render/dynamic-rendering' */ export function unstable_noStore() { const callingExpression = 'unstable_noStore()' - const store = getExpectedStaticGenerationStore(callingExpression, false) - // This generally implies we are being called in Pages Router. We should probably not support - // unstable_noStore in contexts outside of `react-server` condition but since we historically - // have not errored here previously, we maintain that behavior for now. - if (!store) return - else if (store.forceStatic) return - store.isUnstableNoStore = true - markCurrentScopeAsDynamic(store, callingExpression) + const store = staticGenerationAsyncStorage.getStore() + if (!store) { + // This generally implies we are being called in Pages router. We should probably not support + // unstable_noStore in contexts outside of `react-server` condition but since we historically + // have not errored here previously, we maintain that behavior for now. + return + } else if (store.forceStatic) { + return + } else { + store.isUnstableNoStore = true + markCurrentScopeAsDynamic(store, callingExpression) + } }