Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ed60000
[Breaking] Remove deprecated sync access to Dynamic APIs
devjiwonchoi Sep 24, 2025
e62498e
update errors.json
devjiwonchoi Sep 24, 2025
54fdce2
Revert warn -> throw
devjiwonchoi Sep 24, 2025
bc856f6
test: cast `as unknown as UnsafeUnwrapped` to `as any`
devjiwonchoi Sep 24, 2025
7981dd5
test: update snapshot
devjiwonchoi Sep 24, 2025
a72fc22
test: update snapshot for dev
devjiwonchoi Sep 24, 2025
e401bdb
test: update snapshot
devjiwonchoi Sep 24, 2025
6096042
remove untracked "exotic" functions
devjiwonchoi Sep 24, 2025
453f6f2
test: update failed tests
devjiwonchoi Sep 24, 2025
1fddc22
test: add comment about as any type cast
devjiwonchoi Sep 24, 2025
bb92601
test: remove prod assertion for not error on build
devjiwonchoi Sep 24, 2025
604ada6
fix: port throw SSG bailout with dynamic error from exotic
devjiwonchoi Sep 24, 2025
14e40d8
test: ppr-full had sync headers access
devjiwonchoi Sep 24, 2025
044005e
test: update cache-components-errors snapshot
devjiwonchoi Sep 24, 2025
968bd5c
preserve makeDynamicallyTrackedSearchParamsWithDevWarnings
devjiwonchoi Sep 25, 2025
e6cc7cc
test: update test/production/standalone-mode/required-server-files/ap…
devjiwonchoi Sep 25, 2025
27e76c0
fix: should use promiseInitialized
devjiwonchoi Sep 25, 2025
9433294
bring back proxiedProperties check
devjiwonchoi Sep 25, 2025
d26436c
use workStoreUnit to throw
devjiwonchoi Sep 25, 2025
3aa59e6
update warnings
devjiwonchoi Sep 25, 2025
a4c5d52
update test snapshots
devjiwonchoi Sep 25, 2025
103b102
docs: remove sync exception
devjiwonchoi Sep 25, 2025
d297247
update errors.json
devjiwonchoi Sep 25, 2025
c321b23
remove exoticness from makeErroring functions
devjiwonchoi Sep 25, 2025
e9d49cd
Remove enumeration warnings
devjiwonchoi Sep 26, 2025
e2523fb
test: remove searchParams fail build outside render context
devjiwonchoi Sep 26, 2025
245d944
Remove warning about allowing sync access and update test
devjiwonchoi Sep 26, 2025
4e4e12d
Remove unused throwWithStaticGenerationBailoutError
devjiwonchoi Sep 26, 2025
88a16b0
Remove has and ownKeys from makeErroringSearchParamsForUseCache
devjiwonchoi Sep 26, 2025
883ee30
Remove"Dynamic API is a Promise..." error message
devjiwonchoi Sep 26, 2025
bbdbb98
Remove unnecessary unproxiedProperties
devjiwonchoi Sep 26, 2025
8c9655a
"returns" a Promise for headers, cookies, draftMode
devjiwonchoi Sep 26, 2025
3e2100b
update errors.json
devjiwonchoi Sep 26, 2025
05054e9
Update warn/errors to call `()` if is a function
devjiwonchoi Sep 26, 2025
abdca6c
update errors.json
devjiwonchoi Sep 26, 2025
921608b
test: update error code
devjiwonchoi Sep 27, 2025
5f4a98c
Remove sync warning message from dynamic should errors
devjiwonchoi Sep 27, 2025
7857245
update errors.json
devjiwonchoi Sep 27, 2025
eb8dd5d
test: update error code
devjiwonchoi Sep 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions errors/sync-dynamic-apis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ function Page({ params }) {
This also includes enumerating (e.g. `{...params}`, or `Object.keys(params)`) or iterating over the return
value of these APIs (e.g. `[...headers()]` or `for (const cookie of cookies())`, or explicitly with `cookies()[Symbol.iterator]()`).

In the version of Next.js that issued this warning, access to these properties is still possible directly but will warn.
In future versions, these APIs will be async and direct access will not work as expected.

## Possible Ways to Fix It

The [`next-async-request-api` codemod](/docs/app/guides/upgrading/codemods#next-async-request-api) can fix many of these cases automatically:
Expand Down
25 changes: 24 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -824,5 +824,28 @@
"823": "Timeout waiting for error state from frontend. The browser may not be responding to HMR messages.",
"824": "URL is required in MCP browser response. This is a bug in Next.js.",
"825": "Timeout waiting for response from frontend. The browser may not be responding to HMR messages.",
"826": "unknown bundler: %s"
"826": "unknown bundler: %s",
"827": "Route %s used \\`connection()\\` inside \\`after()\\`. The \\`connection()\\` function is used to indicate the subsequent code must only run when there is an actual Request, but \\`after()\\` executes after the request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/canary/app/api-reference/functions/after",
"828": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`headers()\\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering",
"829": "Route %s used \"%s\" inside \"use cache\". The enabled status of \\`draftMode()\\` can be read in caches but you must not enable or disable \\`draftMode()\\` inside a cache. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
"830": "%sused %s. \\`cookies()\\` returns a Promise and must be unwrapped with \\`await\\` or \\`React.use()\\` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis",
"831": "Route %s used \\`cookies()\\` inside \"use cache\". Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use \\`cookies()\\` outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
"832": "%s must not be used within a Client Component. Next.js should be preventing %s from being included in Client Components statically, but did not in this case.",
"833": "Route %s used \\`headers()\\` inside \"use cache\". Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use \\`headers()\\` outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
"834": "%sused %s. \\`params\\` is a Promise and must be unwrapped with \\`await\\` or \\`React.use()\\` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis",
"835": "%sused %s. \\`draftMode()\\` returns a Promise and must be unwrapped with \\`await\\` or \\`React.use()\\` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis",
"836": "%sused %s. \\`headers()\\` returns a Promise and must be unwrapped with \\`await\\` or \\`React.use()\\` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis",
"837": "Route %s used \\`connection()\\` inside \"use cache: private\". The \\`connection()\\` function is used to indicate the subsequent code must only run when there is an actual navigation request, but caches must be able to be produced before a navigation request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
"838": "Route %s used \\`headers()\\` inside a function cached with \\`unstable_cache()\\`. Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use \\`headers()\\` outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache",
"839": "Route %s used \\`headers()\\` inside \\`after()\\`. This is not supported. If you need this data inside an \\`after()\\` callback, use \\`headers()\\` outside of the callback. See more info here: https://nextjs.org/docs/canary/app/api-reference/functions/after",
"840": "Route %s used \\`connection()\\` inside a function cached with \\`unstable_cache()\\`. The \\`connection()\\` function is used to indicate the subsequent code must only run when there is an actual Request, but caches must be able to be produced before a Request so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache",
"841": "Route %s used \\`connection()\\` inside \"use cache\". The \\`connection()\\` function is used to indicate the subsequent code must only run when there is an actual request, but caches must be able to be produced before a request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
"842": "Route %s used \\`searchParams\\` inside \"use cache\". Accessing dynamic request data inside a cache scope is not supported. If you need some search params inside a cached function await \\`searchParams\\` outside of the cached function and pass only the required search params as arguments to the cached function. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache",
"843": "Route %s used \\`cookies()\\` inside \\`after()\\`. This is not supported. If you need this data inside an \\`after()\\` callback, use \\`cookies()\\` outside of the callback. See more info here: https://nextjs.org/docs/canary/app/api-reference/functions/after",
"844": "Route %s used \"%s\" inside a function cached with \\`unstable_cache()\\`. The enabled status of \\`draftMode()\\` can be read in caches but you must not enable or disable \\`draftMode()\\` inside a cache. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache",
"845": "Route %s used \"%s\" inside \\`after()\\`. The enabled status of \\`draftMode()\\` can be read inside \\`after()\\` but you cannot enable or disable \\`draftMode()\\`. See more info here: https://nextjs.org/docs/app/api-reference/functions/after",
"846": "Route %s used \\`cookies()\\` inside a function cached with \\`unstable_cache()\\`. Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use \\`cookies()\\` outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache",
"847": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`connection()\\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering",
"848": "%sused %s. \\`searchParams\\` is a Promise and must be unwrapped with \\`await\\` or \\`React.use()\\` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis",
"849": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`cookies()\\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering"
}
2 changes: 0 additions & 2 deletions packages/next/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,3 @@ export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types'
export { after } from 'next/dist/server/after'
export { unstable_rootParams } from 'next/dist/server/request/root-params'
export { connection } from 'next/dist/server/request/connection'
export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params'
export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params'
Original file line number Diff line number Diff line change
Expand Up @@ -423,8 +423,6 @@ function createServerDefinitions(
export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types'
export { after } from 'next/dist/server/after'
export { connection } from 'next/dist/server/request/connection'
export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params'
export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params'
export function unstable_rootParams(): Promise<{ ${rootParams
.map(
({ param, optional }) =>
Expand Down
114 changes: 11 additions & 103 deletions packages/next/src/client/request/params.browser.dev.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Params } from '../../server/request/params'

import { ReflectAdapter } from '../../server/web/spec-extension/adapters/reflect'
import { InvariantError } from '../../shared/lib/invariant-error'
import {
describeStringPropertyAccess,
wellKnownProperties,
Expand All @@ -10,63 +9,6 @@ import {
interface CacheLifetime {}
const CachedParams = new WeakMap<CacheLifetime, Promise<Params>>()

function makeDynamicallyTrackedExoticParamsWithDevWarnings(
underlyingParams: Params
): Promise<Params> {
const cachedParams = CachedParams.get(underlyingParams)
if (cachedParams) {
return cachedParams
}

// We don't use makeResolvedReactPromise here because params
// supports copying with spread and we don't want to unnecessarily
// instrument the promise with spreadable properties of ReactPromise.
const promise = Promise.resolve(underlyingParams)

const proxiedProperties = new Set<string>()
const unproxiedProperties: Array<string> = []

Object.keys(underlyingParams).forEach((prop) => {
if (wellKnownProperties.has(prop)) {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
} else {
proxiedProperties.add(prop)
;(promise as any)[prop] = underlyingParams[prop]
}
})

const proxiedPromise = new Proxy(promise, {
get(target, prop, receiver) {
if (typeof prop === 'string') {
if (
// We are accessing a property that was proxied to the promise instance
proxiedProperties.has(prop)
) {
const expression = describeStringPropertyAccess('params', prop)
warnForSyncAccess(expression)
}
}
return ReflectAdapter.get(target, prop, receiver)
},
set(target, prop, value, receiver) {
if (typeof prop === 'string') {
proxiedProperties.delete(prop)
}
return ReflectAdapter.set(target, prop, value, receiver)
},
ownKeys(target) {
warnForEnumeration(unproxiedProperties)
return Reflect.ownKeys(target)
},
})

CachedParams.set(underlyingParams, proxiedPromise)
return proxiedPromise
}

// Similar to `makeDynamicallyTrackedExoticParamsWithDevWarnings`, but just
// logging the sync access without actually defining the params on the promise.
function makeDynamicallyTrackedParamsWithDevWarnings(
underlyingParams: Params
): Promise<Params> {
Expand All @@ -81,7 +23,6 @@ function makeDynamicallyTrackedParamsWithDevWarnings(
const promise = Promise.resolve(underlyingParams)

const proxiedProperties = new Set<string>()
const unproxiedProperties: Array<string> = []

Object.keys(underlyingParams).forEach((prop) => {
if (wellKnownProperties.has(prop)) {
Expand Down Expand Up @@ -112,7 +53,7 @@ function makeDynamicallyTrackedParamsWithDevWarnings(
return ReflectAdapter.set(target, prop, value, receiver)
},
ownKeys(target) {
warnForEnumeration(unproxiedProperties)
warnForEnumeration()
return Reflect.ownKeys(target)
},
})
Expand All @@ -123,55 +64,22 @@ function makeDynamicallyTrackedParamsWithDevWarnings(

function warnForSyncAccess(expression: string) {
console.error(
`A param property was accessed directly with ${expression}. \`params\` is now a Promise and should be unwrapped with \`React.use()\` before accessing properties of the underlying params object. In this version of Next.js direct access to param properties is still supported to facilitate migration but in a future version you will be required to unwrap \`params\` with \`React.use()\`.`
`A param property was accessed directly with ${expression}. ` +
`\`params\` is a Promise and must be unwrapped with \`React.use()\` before accessing its properties. ` +
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
)
}

function warnForEnumeration(missingProperties: Array<string>) {
if (missingProperties.length) {
const describedMissingProperties =
describeListOfPropertyNames(missingProperties)
console.error(
`params are being enumerated incompletely missing these properties: ${describedMissingProperties}. ` +
`\`params\` should be unwrapped with \`React.use()\` before using its value. ` +
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
)
} else {
console.error(
`params are being enumerated. ` +
`\`params\` should be unwrapped with \`React.use()\` before using its value. ` +
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
)
}
}

function describeListOfPropertyNames(properties: Array<string>) {
switch (properties.length) {
case 0:
throw new InvariantError(
'Expected describeListOfPropertyNames to be called with a non-empty list of strings.'
)
case 1:
return `\`${properties[0]}\``
case 2:
return `\`${properties[0]}\` and \`${properties[1]}\``
default: {
let description = ''
for (let i = 0; i < properties.length - 1; i++) {
description += `\`${properties[i]}\`, `
}
description += `, and \`${properties[properties.length - 1]}\``
return description
}
}
function warnForEnumeration() {
console.error(
`params are being enumerated. ` +
`\`params\` is a Promise and must be unwrapped with \`React.use()\` before accessing its properties. ` +
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
)
}

export function createRenderParamsFromClient(
clientParams: Params
): Promise<Params> {
if (process.env.__NEXT_CACHE_COMPONENTS) {
return makeDynamicallyTrackedParamsWithDevWarnings(clientParams)
}

return makeDynamicallyTrackedExoticParamsWithDevWarnings(clientParams)
return makeDynamicallyTrackedParamsWithDevWarnings(clientParams)
}
28 changes: 1 addition & 27 deletions packages/next/src/client/request/params.browser.prod.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
import type { Params } from '../../server/request/params'
import { wellKnownProperties } from '../../shared/lib/utils/reflect-utils'

interface CacheLifetime {}
const CachedParams = new WeakMap<CacheLifetime, Promise<Params>>()

function makeUntrackedExoticParams(underlyingParams: Params): Promise<Params> {
const cachedParams = CachedParams.get(underlyingParams)
if (cachedParams) {
return cachedParams
}

const promise = Promise.resolve(underlyingParams)
CachedParams.set(underlyingParams, promise)

Object.keys(underlyingParams).forEach((prop) => {
if (wellKnownProperties.has(prop)) {
// These properties cannot be shadowed because they need to be the
// true underlying value for Promises to work correctly at runtime
} else {
;(promise as any)[prop] = underlyingParams[prop]
}
})

return promise
}

function makeUntrackedParams(underlyingParams: Params): Promise<Params> {
const cachedParams = CachedParams.get(underlyingParams)
if (cachedParams) {
Expand All @@ -40,9 +18,5 @@ function makeUntrackedParams(underlyingParams: Params): Promise<Params> {
export function createRenderParamsFromClient(
clientParams: Params
): Promise<Params> {
if (process.env.__NEXT_CACHE_COMPONENTS) {
return makeUntrackedParams(clientParams)
}

return makeUntrackedExoticParams(clientParams)
return makeUntrackedParams(clientParams)
}
Loading
Loading