From a647e98458dde6a96d976f1663241bb6003574ac Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 21 Feb 2025 10:23:08 +0100 Subject: [PATCH] Add `fallbackExpire` to prerender manifest --- packages/next/src/build/index.ts | 20 ++++++++---- test/e2e/app-dir/use-cache/app/[id]/page.tsx | 7 ++++- test/e2e/app-dir/use-cache/use-cache.test.ts | 32 ++++++++++---------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 44187bd82855bd..70c39c50987519 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -279,6 +279,11 @@ export interface DynamicPrerenderManifestRoute { */ fallbackRevalidate: Revalidate | undefined + /** + * When defined, it describes the expire configuration for the fallback route. + */ + fallbackExpire: Revalidate | undefined + /** * The headers that should used when serving the fallback. */ @@ -3037,12 +3042,13 @@ export default async function build( const fallbackMode = route.fallbackMode ?? FallbackMode.NOT_FOUND - // When we're configured to serve a prerender, we should use the - // fallback revalidate from the export result. If it can't be - // found, mark that we should keep the shell forever (`false`). - let fallbackRevalidate: Revalidate | undefined = + // When the route is configured to serve a prerender, we should + // use the cache control from the export result. If it can't be + // found, mark that we should keep the shell forever + // (revalidate: `false` via `getCacheControl()`). + const fallbackCacheControl = isRoutePPREnabled && fallbackMode === FallbackMode.PRERENDER - ? cacheControl.revalidate + ? cacheControl : undefined const fallback: Fallback = fallbackModeToFallbackField( @@ -3072,7 +3078,8 @@ export default async function build( ), dataRoute, fallback, - fallbackRevalidate, + fallbackRevalidate: fallbackCacheControl?.revalidate, + fallbackExpire: fallbackCacheControl?.expire, fallbackStatus: meta.status, fallbackHeaders: meta.headers, fallbackRootParams: route.fallbackRootParams, @@ -3521,6 +3528,7 @@ export default async function build( ? `${normalizedRoute}.html` : false, fallbackRevalidate: undefined, + fallbackExpire: undefined, fallbackSourceRoute: undefined, fallbackRootParams: undefined, dataRouteRegex: normalizeRouteRegex( diff --git a/test/e2e/app-dir/use-cache/app/[id]/page.tsx b/test/e2e/app-dir/use-cache/app/[id]/page.tsx index 74f4ed6d08dc30..7ba0b38ddee352 100644 --- a/test/e2e/app-dir/use-cache/app/[id]/page.tsx +++ b/test/e2e/app-dir/use-cache/app/[id]/page.tsx @@ -1,5 +1,8 @@ +import { unstable_cacheLife } from 'next/cache' + async function getCachedRandom(n: number) { 'use cache' + unstable_cacheLife('weeks') return String(Math.ceil(Math.random() * n)) } @@ -11,5 +14,7 @@ export async function generateStaticParams() { } export default async function Page() { - return 'hit' + const value = getCachedRandom(1) + + return

{value}

} diff --git a/test/e2e/app-dir/use-cache/use-cache.test.ts b/test/e2e/app-dir/use-cache/use-cache.test.ts index 38c7e698454224..95299bfae9239e 100644 --- a/test/e2e/app-dir/use-cache/use-cache.test.ts +++ b/test/e2e/app-dir/use-cache/use-cache.test.ts @@ -7,6 +7,7 @@ import { createRenderResumeDataCache, RenderResumeDataCache, } from 'next/dist/server/resume-data-cache/resume-data-cache' +import { PrerenderManifest } from 'next/dist/build' const GENERIC_RSC_ERROR = 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.' @@ -371,29 +372,28 @@ describe('use-cache', () => { }) it('should match the expected revalidate and expire configs on the prerender manifest', async () => { - const prerenderManifest = JSON.parse( + const { version, routes, dynamicRoutes } = JSON.parse( await next.readFile('.next/prerender-manifest.json') - ) + ) as PrerenderManifest - expect(prerenderManifest.version).toBe(4) + expect(version).toBe(4) - expect( - prerenderManifest.routes['/cache-life'].initialRevalidateSeconds - ).toBe(100) - - expect(prerenderManifest.routes['/cache-life'].initialExpireSeconds).toBe( - 250 - ) + // custom cache life profile "frequent" + expect(routes['/cache-life'].initialRevalidateSeconds).toBe(100) + expect(routes['/cache-life'].initialExpireSeconds).toBe(250) - expect( - prerenderManifest.routes['/cache-fetch'].initialExpireSeconds - ).toBe(31536000) // default expireTime + // default expireTime + expect(routes['/cache-fetch'].initialExpireSeconds).toBe(31536000) // The revalidate config from the fetch call should lower the revalidate // config for the page. - expect( - prerenderManifest.routes['/cache-tag'].initialRevalidateSeconds - ).toBe(42) + expect(routes['/cache-tag'].initialRevalidateSeconds).toBe(42) + + if (process.env.__NEXT_EXPERIMENTAL_PPR === 'true') { + // cache life profile "weeks" + expect(dynamicRoutes['/[id]'].fallbackRevalidate).toBe(604800) + expect(dynamicRoutes['/[id]'].fallbackExpire).toBe(2592000) + } }) it('should match the expected stale config in the page header', async () => {