diff --git a/errors/next-dynamic-api-wrong-context.mdx b/errors/next-dynamic-api-wrong-context.mdx new file mode 100644 index 0000000000000..064baba8828e0 --- /dev/null +++ b/errors/next-dynamic-api-wrong-context.mdx @@ -0,0 +1,45 @@ +--- +title: Dynamic API was called outside request +--- + +#### Why This Error Occurred + +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 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/packages/next/src/client/components/headers.ts b/packages/next/src/client/components/headers.ts index abd0780734542..38432c01bc31d 100644 --- a/packages/next/src/client/components/headers.ts +++ b/packages/next/src/client/components/headers.ts @@ -33,8 +33,7 @@ export function headers() { } } - const requestStore = getExpectedRequestStore(callingExpression) - return requestStore.headers + return getExpectedRequestStore(callingExpression).headers } export function cookies() { @@ -54,10 +53,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 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..7707485bccb7a 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,8 @@ 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 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 ace72ad9d9bae..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,13 +55,3 @@ export type StaticGenerationAsyncStorage = export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = createAsyncLocalStorage() - -export function getExpectedStaticGenerationStore(callingExpression: string) { - const store = staticGenerationAsyncStorage.getStore() - if (!store) { - throw new Error( - `Invariant: \`${callingExpression}\` expects to have staticGenerationAsyncStorage, none available.` - ) - } - return store -} 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..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' @@ -62,8 +59,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 +93,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/test/development/acceptance-app/rsc-runtime-errors.test.ts b/test/development/acceptance-app/rsc-runtime-errors.test.ts index 5847497cead14..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: Invariant: \`cookies\` expects to have requestAsyncStorage, none available.` + 'Error: `cookies` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context' ) })