diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index c3e1d3e87b8ed..4cf35c54fb399 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -1128,7 +1128,7 @@ async function renderToHTMLOrFlightImpl( ]) } - addImplicitTags(workStore, requestStore, undefined) + addImplicitTags(workStore, requestStore, undefined, undefined) if (workStore.tags) { metadata.fetchTags = workStore.tags.join(',') @@ -1237,7 +1237,7 @@ async function renderToHTMLOrFlightImpl( ]) } - addImplicitTags(workStore, requestStore, undefined) + addImplicitTags(workStore, requestStore, undefined, undefined) if (workStore.tags) { metadata.fetchTags = workStore.tags.join(',') @@ -1852,6 +1852,7 @@ async function prerenderToStream( const cacheSignal = new CacheSignal() const prospectiveRenderPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, cacheSignal, // During the prospective render we don't want to synchronously abort on dynamic access // because it could prevent us from discovering all caches in siblings. So we omit the controller @@ -1943,6 +1944,7 @@ async function prerenderToStream( const finalRenderPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, // During the final prerender we don't need to track cache access so we omit the signal cacheSignal: null, // During the final render we do want to abort synchronously on dynamic access so we @@ -1998,6 +2000,7 @@ async function prerenderToStream( const SSRController = new AbortController() const ssrPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, // For HTML Generation we don't need to track cache reads (RSC only) cacheSignal: null, // We expect the SSR render to complete in a single Task and need to be able to synchronously abort @@ -2202,6 +2205,7 @@ async function prerenderToStream( const cacheSignal = new CacheSignal() const prospectiveRenderPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, cacheSignal, // When PPR is off we can synchronously abort the prospective render because we will // always hit this path on the final render and thus we can skip the final render and just @@ -2280,6 +2284,7 @@ async function prerenderToStream( const finalRenderPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, // During the final prerender we don't need to track cache access so we omit the signal cacheSignal: null, controller: flightController, @@ -2289,6 +2294,7 @@ async function prerenderToStream( const SSRController = new AbortController() const ssrPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, // For HTML Generation we don't need to track cache reads (RSC only) cacheSignal: null, // We expect the SSR render to complete in a single Task and need to be able to synchronously abort @@ -2469,6 +2475,7 @@ async function prerenderToStream( ) const reactServerPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, cacheSignal: null, controller: null, dynamicTracking, @@ -2496,6 +2503,7 @@ async function prerenderToStream( const ssrPrerenderStore: PrerenderStore = { type: 'prerender', + pathname: ctx.requestStore.url.pathname, cacheSignal: null, controller: null, dynamicTracking, @@ -2653,6 +2661,7 @@ async function prerenderToStream( } else { const prerenderLegacyStore: PrerenderStore = { type: 'prerender-legacy', + pathname: ctx.requestStore.url.pathname, } // This is a regular static generation. We don't do dynamic tracking because we rely on // the old-school dynamic error handling to bail out of static generation diff --git a/packages/next/src/server/app-render/prerender-async-storage.external.ts b/packages/next/src/server/app-render/prerender-async-storage.external.ts index d118b4cde5845..69690b8fcb72c 100644 --- a/packages/next/src/server/app-render/prerender-async-storage.external.ts +++ b/packages/next/src/server/app-render/prerender-async-storage.external.ts @@ -18,6 +18,7 @@ import { prerenderAsyncStorage } from './prerender-async-storage-instance' with */ export type PrerenderStoreModern = { type: 'prerender' + pathname: string | undefined /** * This is the AbortController passed to React. It can be used to abort the prerender * if we encounter conditions that do not require further rendering @@ -38,6 +39,7 @@ export type PrerenderStoreModern = { export type PrerenderStoreLegacy = { type: 'prerender-legacy' + pathname: string | undefined } export type PrerenderStore = PrerenderStoreLegacy | PrerenderStoreModern diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index e77e1e82a4fa9..6a9e97152fa77 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -18,6 +18,7 @@ import type { RequestAsyncStorage, RequestStore, } from '../../client/components/request-async-storage.external' +import type { PrerenderStore } from '../app-render/prerender-async-storage.external' import { cacheAsyncStorage, type CacheStore, @@ -144,6 +145,7 @@ const getDerivedTags = (pathname: string): string[] => { export function addImplicitTags( workStore: WorkStore, requestStore: RequestStore | undefined, + prerenderStore: PrerenderStore | undefined, cacheStore: CacheStore | undefined ) { const newTags: string[] = [] @@ -174,10 +176,17 @@ export function addImplicitTags( newTags.push(tag) } + const renderedPathname = + requestStore !== undefined + ? requestStore.url.pathname + : prerenderStore !== undefined + ? prerenderStore.pathname + : undefined + // Add the tags from the pathname. If the route has unknown params, we don't // want to add the pathname as a tag, as it will be invalid. - if (requestStore?.url.pathname && !hasFallbackRouteParams) { - const tag = `${NEXT_CACHE_IMPLICIT_TAG_ID}${requestStore.url.pathname}` + if (renderedPathname && !hasFallbackRouteParams) { + const tag = `${NEXT_CACHE_IMPLICIT_TAG_ID}${renderedPathname}` if ( !cacheStore || (cacheStore.type !== 'cache' && cacheStore.type !== 'unstable-cache') @@ -263,6 +272,7 @@ export function createPatchedFetcher( const hideSpan = process.env.NEXT_OTEL_FETCH_DISABLED === '1' const workStore = workAsyncStorage.getStore() + const prerenderStore = prerenderAsyncStorage.getStore() const result = getTracer().trace( isInternal ? NextNodeServerSpan.internalFetch : AppRenderSpan.fetch, @@ -345,6 +355,7 @@ export function createPatchedFetcher( const implicitTags = addImplicitTags( workStore, requestStore, + prerenderStore, cacheStore ) @@ -772,7 +783,6 @@ export function createPatchedFetcher( // We sometimes use the cache to dedupe fetches that do not specify a cache configuration // In these cases we want to make sure we still exclude them from prerenders if dynamicIO is on // so we introduce an artificial Task boundary here. - const prerenderStore = prerenderAsyncStorage.getStore() if (prerenderStore) { await waitAtLeastOneReactRenderTask() } @@ -953,7 +963,6 @@ export function createPatchedFetcher( } ) - const prerenderStore = prerenderAsyncStorage.getStore() if ( prerenderStore && prerenderStore.type === 'prerender' && diff --git a/packages/next/src/server/route-modules/app-route/module.ts b/packages/next/src/server/route-modules/app-route/module.ts index f97674553e2c8..ef07a5c839a67 100644 --- a/packages/next/src/server/route-modules/app-route/module.ts +++ b/packages/next/src/server/route-modules/app-route/module.ts @@ -336,6 +336,7 @@ export class AppRouteRouteModule extends RouteModule< let dynamicTracking = createDynamicTrackingState(undefined) const prospectiveRoutePrerenderStore: PrerenderStore = { type: 'prerender', + pathname: requestStore.url.pathname, cacheSignal, // During prospective render we don't use a controller // because we need to let all caches fill. @@ -403,6 +404,7 @@ export class AppRouteRouteModule extends RouteModule< const finalRoutePrerenderStore: PrerenderStore = { type: 'prerender', + pathname: requestStore.url.pathname, cacheSignal: null, controller, dynamicTracking, @@ -466,6 +468,7 @@ export class AppRouteRouteModule extends RouteModule< res = await prerenderAsyncStorage.run( { type: 'prerender-legacy', + pathname: requestStore.url.pathname, }, handler, request, @@ -522,7 +525,7 @@ export class AppRouteRouteModule extends RouteModule< ...Object.values(workStore.pendingRevalidates || {}), ]) - addImplicitTags(workStore, requestStore, undefined) + addImplicitTags(workStore, requestStore, undefined, undefined) ;(context.renderOpts as any).fetchTags = workStore.tags?.join(',') // It's possible cookies were set in the handler, so we need 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 54df910918a91..20c050aa0fcef 100644 --- a/packages/next/src/server/web/spec-extension/unstable-cache.ts +++ b/packages/next/src/server/web/spec-extension/unstable-cache.ts @@ -196,6 +196,7 @@ export function unstable_cache( const implicitTags = addImplicitTags( workStore, requestStore, + prerenderStore, cacheStore ) @@ -305,7 +306,8 @@ export function unstable_cache( // @TODO check on this API. addImplicitTags mutates the store and returns the implicit tags. The naming // of this function is potentially a little confusing const implicitTags = - workStore && addImplicitTags(workStore, requestStore, cacheStore) + workStore && + addImplicitTags(workStore, requestStore, prerenderStore, cacheStore) const cacheEntry = await incrementalCache.get(cacheKey, { kind: IncrementalCacheKind.FETCH,