Skip to content

Commit 1dda5b6

Browse files
devjiwonchoignoff
andauthored
[Breaking] Remove deprecated sync access to Dynamic APIs (#84179)
This PR removes the deprecated sync access to Dynamic APIs. - Removed UnsafeUnwrapped* types. - Replaced `as unknown as UnsafeUnwrapped ` type casts to `as any` for dev warning. - Removed tests that expected sync access to not error. - Removed `UntrackedExotic` functions. - Modified tests that were accidentally doing sync access. - Updated warnings to emphasize that it is a Promise and must be awaited. - Removed paragraph that states access is allowed from `errors/sync-dynamic-apis.mdx`. --------- Co-authored-by: Josh Story <[email protected]>
1 parent bc14f19 commit 1dda5b6

File tree

41 files changed

+444
-2183
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+444
-2183
lines changed

errors/sync-dynamic-apis.mdx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ function Page({ params }) {
2626
This also includes enumerating (e.g. `{...params}`, or `Object.keys(params)`) or iterating over the return
2727
value of these APIs (e.g. `[...headers()]` or `for (const cookie of cookies())`, or explicitly with `cookies()[Symbol.iterator]()`).
2828

29-
In the version of Next.js that issued this warning, access to these properties is still possible directly but will warn.
30-
In future versions, these APIs will be async and direct access will not work as expected.
31-
3229
## Possible Ways to Fix It
3330

3431
The [`next-async-request-api` codemod](/docs/app/guides/upgrading/codemods#next-async-request-api) can fix many of these cases automatically:

packages/next/errors.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,5 +824,28 @@
824824
"823": "Timeout waiting for error state from frontend. The browser may not be responding to HMR messages.",
825825
"824": "URL is required in MCP browser response. This is a bug in Next.js.",
826826
"825": "Timeout waiting for response from frontend. The browser may not be responding to HMR messages.",
827-
"826": "unknown bundler: %s"
827+
"826": "unknown bundler: %s",
828+
"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",
829+
"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",
830+
"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",
831+
"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",
832+
"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",
833+
"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.",
834+
"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",
835+
"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",
836+
"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",
837+
"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",
838+
"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",
839+
"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",
840+
"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",
841+
"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",
842+
"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",
843+
"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",
844+
"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",
845+
"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",
846+
"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",
847+
"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",
848+
"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",
849+
"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",
850+
"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"
828851
}

packages/next/server.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,3 @@ export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types'
1616
export { after } from 'next/dist/server/after'
1717
export { unstable_rootParams } from 'next/dist/server/request/root-params'
1818
export { connection } from 'next/dist/server/request/connection'
19-
export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params'
20-
export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params'

packages/next/src/build/webpack/plugins/next-types-plugin/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,6 @@ function createServerDefinitions(
423423
export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types'
424424
export { after } from 'next/dist/server/after'
425425
export { connection } from 'next/dist/server/request/connection'
426-
export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params'
427-
export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params'
428426
export function unstable_rootParams(): Promise<{ ${rootParams
429427
.map(
430428
({ param, optional }) =>
Lines changed: 11 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Params } from '../../server/request/params'
22

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

13-
function makeDynamicallyTrackedExoticParamsWithDevWarnings(
14-
underlyingParams: Params
15-
): Promise<Params> {
16-
const cachedParams = CachedParams.get(underlyingParams)
17-
if (cachedParams) {
18-
return cachedParams
19-
}
20-
21-
// We don't use makeResolvedReactPromise here because params
22-
// supports copying with spread and we don't want to unnecessarily
23-
// instrument the promise with spreadable properties of ReactPromise.
24-
const promise = Promise.resolve(underlyingParams)
25-
26-
const proxiedProperties = new Set<string>()
27-
const unproxiedProperties: Array<string> = []
28-
29-
Object.keys(underlyingParams).forEach((prop) => {
30-
if (wellKnownProperties.has(prop)) {
31-
// These properties cannot be shadowed because they need to be the
32-
// true underlying value for Promises to work correctly at runtime
33-
} else {
34-
proxiedProperties.add(prop)
35-
;(promise as any)[prop] = underlyingParams[prop]
36-
}
37-
})
38-
39-
const proxiedPromise = new Proxy(promise, {
40-
get(target, prop, receiver) {
41-
if (typeof prop === 'string') {
42-
if (
43-
// We are accessing a property that was proxied to the promise instance
44-
proxiedProperties.has(prop)
45-
) {
46-
const expression = describeStringPropertyAccess('params', prop)
47-
warnForSyncAccess(expression)
48-
}
49-
}
50-
return ReflectAdapter.get(target, prop, receiver)
51-
},
52-
set(target, prop, value, receiver) {
53-
if (typeof prop === 'string') {
54-
proxiedProperties.delete(prop)
55-
}
56-
return ReflectAdapter.set(target, prop, value, receiver)
57-
},
58-
ownKeys(target) {
59-
warnForEnumeration(unproxiedProperties)
60-
return Reflect.ownKeys(target)
61-
},
62-
})
63-
64-
CachedParams.set(underlyingParams, proxiedPromise)
65-
return proxiedPromise
66-
}
67-
68-
// Similar to `makeDynamicallyTrackedExoticParamsWithDevWarnings`, but just
69-
// logging the sync access without actually defining the params on the promise.
7012
function makeDynamicallyTrackedParamsWithDevWarnings(
7113
underlyingParams: Params
7214
): Promise<Params> {
@@ -81,7 +23,6 @@ function makeDynamicallyTrackedParamsWithDevWarnings(
8123
const promise = Promise.resolve(underlyingParams)
8224

8325
const proxiedProperties = new Set<string>()
84-
const unproxiedProperties: Array<string> = []
8526

8627
Object.keys(underlyingParams).forEach((prop) => {
8728
if (wellKnownProperties.has(prop)) {
@@ -112,7 +53,7 @@ function makeDynamicallyTrackedParamsWithDevWarnings(
11253
return ReflectAdapter.set(target, prop, value, receiver)
11354
},
11455
ownKeys(target) {
115-
warnForEnumeration(unproxiedProperties)
56+
warnForEnumeration()
11657
return Reflect.ownKeys(target)
11758
},
11859
})
@@ -123,55 +64,22 @@ function makeDynamicallyTrackedParamsWithDevWarnings(
12364

12465
function warnForSyncAccess(expression: string) {
12566
console.error(
126-
`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()\`.`
67+
`A param property was accessed directly with ${expression}. ` +
68+
`\`params\` is a Promise and must be unwrapped with \`React.use()\` before accessing its properties. ` +
69+
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
12770
)
12871
}
12972

130-
function warnForEnumeration(missingProperties: Array<string>) {
131-
if (missingProperties.length) {
132-
const describedMissingProperties =
133-
describeListOfPropertyNames(missingProperties)
134-
console.error(
135-
`params are being enumerated incompletely missing these properties: ${describedMissingProperties}. ` +
136-
`\`params\` should be unwrapped with \`React.use()\` before using its value. ` +
137-
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
138-
)
139-
} else {
140-
console.error(
141-
`params are being enumerated. ` +
142-
`\`params\` should be unwrapped with \`React.use()\` before using its value. ` +
143-
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
144-
)
145-
}
146-
}
147-
148-
function describeListOfPropertyNames(properties: Array<string>) {
149-
switch (properties.length) {
150-
case 0:
151-
throw new InvariantError(
152-
'Expected describeListOfPropertyNames to be called with a non-empty list of strings.'
153-
)
154-
case 1:
155-
return `\`${properties[0]}\``
156-
case 2:
157-
return `\`${properties[0]}\` and \`${properties[1]}\``
158-
default: {
159-
let description = ''
160-
for (let i = 0; i < properties.length - 1; i++) {
161-
description += `\`${properties[i]}\`, `
162-
}
163-
description += `, and \`${properties[properties.length - 1]}\``
164-
return description
165-
}
166-
}
73+
function warnForEnumeration() {
74+
console.error(
75+
`params are being enumerated. ` +
76+
`\`params\` is a Promise and must be unwrapped with \`React.use()\` before accessing its properties. ` +
77+
`Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis`
78+
)
16779
}
16880

16981
export function createRenderParamsFromClient(
17082
clientParams: Params
17183
): Promise<Params> {
172-
if (process.env.__NEXT_CACHE_COMPONENTS) {
173-
return makeDynamicallyTrackedParamsWithDevWarnings(clientParams)
174-
}
175-
176-
return makeDynamicallyTrackedExoticParamsWithDevWarnings(clientParams)
84+
return makeDynamicallyTrackedParamsWithDevWarnings(clientParams)
17785
}
Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,8 @@
11
import type { Params } from '../../server/request/params'
2-
import { wellKnownProperties } from '../../shared/lib/utils/reflect-utils'
32

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

7-
function makeUntrackedExoticParams(underlyingParams: Params): Promise<Params> {
8-
const cachedParams = CachedParams.get(underlyingParams)
9-
if (cachedParams) {
10-
return cachedParams
11-
}
12-
13-
const promise = Promise.resolve(underlyingParams)
14-
CachedParams.set(underlyingParams, promise)
15-
16-
Object.keys(underlyingParams).forEach((prop) => {
17-
if (wellKnownProperties.has(prop)) {
18-
// These properties cannot be shadowed because they need to be the
19-
// true underlying value for Promises to work correctly at runtime
20-
} else {
21-
;(promise as any)[prop] = underlyingParams[prop]
22-
}
23-
})
24-
25-
return promise
26-
}
27-
286
function makeUntrackedParams(underlyingParams: Params): Promise<Params> {
297
const cachedParams = CachedParams.get(underlyingParams)
308
if (cachedParams) {
@@ -40,9 +18,5 @@ function makeUntrackedParams(underlyingParams: Params): Promise<Params> {
4018
export function createRenderParamsFromClient(
4119
clientParams: Params
4220
): Promise<Params> {
43-
if (process.env.__NEXT_CACHE_COMPONENTS) {
44-
return makeUntrackedParams(clientParams)
45-
}
46-
47-
return makeUntrackedExoticParams(clientParams)
21+
return makeUntrackedParams(clientParams)
4822
}

0 commit comments

Comments
 (0)