Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -1137,5 +1137,7 @@
"1136": "Page \"%s\" cannot use both \\`export const unstable_dynamicStaleTime\\` and \\`export const unstable_instant\\`.",
"1137": "\"%s\" cannot use \\`export const unstable_dynamicStaleTime\\`. This config is only supported in page files, not layouts.",
"1138": "`unstable_retry()` can only be used in the App Router. Use `reset()` in the Pages Router.",
"1139": "`unstable_catchError` can only be used in Client Components."
"1139": "`unstable_catchError` can only be used in Client Components.",
"1140": "Route %s used \\`import('next/root-params').%s()\\` inside \\`\"use cache\"\\` nested within \\`unstable_cache\\`. Root params are not available in this context.",
"1141": "Route %s used \\`import('next/root-params').%s()\\` inside \\`unstable_cache\\`. This is not supported. Use \\`\"use cache\"\\` instead."
}
1 change: 1 addition & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const NEXT_CACHE_TAG_MAX_ITEMS = 128
export const NEXT_CACHE_TAG_MAX_LENGTH = 256
export const NEXT_CACHE_SOFT_TAG_MAX_LENGTH = 1024
export const NEXT_CACHE_IMPLICIT_TAG_ID = '_N_T_'
export const NEXT_CACHE_ROOT_PARAM_TAG_ID = '_N_RP_'

// in seconds
export const CACHE_ONE_YEAR_SECONDS = 31536000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('getDynamicHTMLPostponedState', () => {
},
hasExplicitRevalidate: true,
hasExplicitExpire: true,
readRootParamNames: undefined,
})
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,19 @@ export interface CommonUseCacheStore extends CommonCacheStore, RevalidateStore {

export interface PublicUseCacheStore extends CommonUseCacheStore {
readonly type: 'cache'

/**
* The root params for the current route. `undefined` when nested inside
* `unstable_cache`, which doesn't carry root params. Currently, `"use cache"`
* inside `unstable_cache` is allowed, so this case must be handled. The error
* message in `getRootParam` assumes this is the only scenario where
* `rootParams` is `undefined`.
*/
readonly rootParams: Params | undefined
/**
* Tracks which root param names were read during this cache invocation.
*/
readonly readRootParamNames: Set<string>
}

export interface PrivateUseCacheStore extends CommonUseCacheStore {
Expand All @@ -354,9 +367,8 @@ export interface PrivateUseCacheStore extends CommonUseCacheStore {
readonly cookies: ReadonlyRequestCookies

/**
* Private caches don't currently need to track root params in the cache key
* because they're not persisted anywhere, so we can allow root params access
* (unlike public caches)
* Private caches don't currently need to track read root params for the cache
* key because they're not persisted anywhere.
*/
readonly rootParams: Params
}
Expand All @@ -365,6 +377,12 @@ export type UseCacheStore = PublicUseCacheStore | PrivateUseCacheStore

export interface UnstableCacheStore extends CommonCacheStore {
readonly type: 'unstable-cache'
/**
* Always `undefined` for `unstable_cache` — root params are not available in
* this context. If a `"use cache"` function nested inside `unstable_cache`
* tries to access root params, it will encounter `undefined` here and throw.
*/
readonly rootParams: undefined
}

/**
Expand Down
14 changes: 11 additions & 3 deletions packages/next/src/server/request/root-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,20 @@ export function getRootParam(paramName: string): Promise<ParamValue> {
}

switch (workUnitStore.type) {
case 'unstable-cache':
case 'cache': {
case 'unstable-cache': {
throw new Error(
`Route ${workStore.route} used ${apiName} inside \`"use cache"\` or \`unstable_cache\`. Support for this API inside cache scopes is planned for a future version of Next.js.`
`Route ${workStore.route} used ${apiName} inside \`unstable_cache\`. This is not supported. Use \`"use cache"\` instead.`
)
}
case 'cache': {
if (!workUnitStore.rootParams) {
throw new Error(
`Route ${workStore.route} used ${apiName} inside \`"use cache"\` nested within \`unstable_cache\`. Root params are not available in this context.`
)
}
workUnitStore.readRootParamNames.add(paramName)
return Promise.resolve(workUnitStore.rootParams[paramName])
}
case 'prerender':
case 'prerender-ppr':
case 'prerender-legacy': {
Expand Down
84 changes: 49 additions & 35 deletions packages/next/src/server/resume-data-cache/cache-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface UseCacheCacheStoreSerialized {
}
hasExplicitRevalidate: boolean | undefined
hasExplicitExpire: boolean | undefined
readRootParamNames: string[] | undefined
}

/**
Expand All @@ -64,7 +65,7 @@ export function parseUseCacheCacheStore(

for (const [
key,
{ entry, hasExplicitRevalidate, hasExplicitExpire },
{ entry, hasExplicitRevalidate, hasExplicitExpire, readRootParamNames },
] of entries) {
store.set(
key,
Expand All @@ -88,6 +89,9 @@ export function parseUseCacheCacheStore(
},
hasExplicitRevalidate,
hasExplicitExpire,
readRootParamNames: readRootParamNames
? new Set(readRootParamNames)
: undefined,
})
)
}
Expand All @@ -107,45 +111,55 @@ export async function serializeUseCacheCacheStore(
return Promise.all(
Array.from(entries).map(([key, value]) => {
return value
.then(async ({ entry, hasExplicitRevalidate, hasExplicitExpire }) => {
if (
isCacheComponentsEnabled &&
(entry.revalidate === 0 || entry.expire < DYNAMIC_EXPIRE)
) {
// The entry was omitted from the prerender result, and subsequently
// does not need to be included in the serialized RDC.
return null
}
.then(
async ({
entry,
hasExplicitRevalidate,
hasExplicitExpire,
readRootParamNames,
}) => {
if (
isCacheComponentsEnabled &&
(entry.revalidate === 0 || entry.expire < DYNAMIC_EXPIRE)
) {
// The entry was omitted from the prerender result, and subsequently
// does not need to be included in the serialized RDC.
return null
}

const [left, right] = entry.value.tee()
entry.value = right
const [left, right] = entry.value.tee()
entry.value = right

let binaryString: string = ''
let binaryString: string = ''

// We want to encode the value as a string, but we aren't sure if the
// value is a a stream of UTF-8 bytes or not, so let's just encode it
// as a string using base64.
for await (const chunk of left) {
binaryString += arrayBufferToString(chunk)
}
// We want to encode the value as a string, but we aren't sure if the
// value is a a stream of UTF-8 bytes or not, so let's just encode it
// as a string using base64.
for await (const chunk of left) {
binaryString += arrayBufferToString(chunk)
}

return [
key,
{
entry: {
// Encode the value as a base64 string.
value: btoa(binaryString),
tags: entry.tags,
stale: entry.stale,
timestamp: entry.timestamp,
expire: entry.expire,
revalidate: entry.revalidate,
return [
key,
{
entry: {
// Encode the value as a base64 string.
value: btoa(binaryString),
tags: entry.tags,
stale: entry.stale,
timestamp: entry.timestamp,
expire: entry.expire,
revalidate: entry.revalidate,
},
hasExplicitRevalidate,
hasExplicitExpire,
readRootParamNames: readRootParamNames
? [...readRootParamNames]
: undefined,
},
hasExplicitRevalidate,
hasExplicitExpire,
},
] satisfies [string, UseCacheCacheStoreSerialized]
})
] satisfies [string, UseCacheCacheStoreSerialized]
}
)
.catch(() => {
// Any failed cache writes should be ignored as to not discard the
// entire cache.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function createMockedCache() {
},
hasExplicitRevalidate: true,
hasExplicitExpire: true,
readRootParamNames: undefined,
})
)

Expand All @@ -42,6 +43,7 @@ function createMockedCache() {
},
hasExplicitRevalidate: true,
hasExplicitExpire: true,
readRootParamNames: undefined,
})
)

Expand All @@ -59,6 +61,7 @@ function createMockedCache() {
},
hasExplicitRevalidate: true,
hasExplicitExpire: true,
readRootParamNames: undefined,
})
)

Expand Down
Loading
Loading