diff --git a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts index 49eea56efd60..174dcd646590 100644 --- a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts @@ -11,7 +11,34 @@ export default class FetchCache implements CacheHandler { private debug: boolean constructor(ctx: CacheHandlerContext) { + this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE + this.headers = {} + this.headers['Content-Type'] = 'application/json' + + if (FETCH_CACHE_HEADER in ctx._requestHeaders) { + const newHeaders = JSON.parse( + ctx._requestHeaders[FETCH_CACHE_HEADER] as string + ) + for (const k in newHeaders) { + this.headers[k] = newHeaders[k] + } + delete ctx._requestHeaders[FETCH_CACHE_HEADER] + } + if (ctx._requestHeaders['x-vercel-sc-host']) { + this.cacheEndpoint = `https://${ctx._requestHeaders['x-vercel-sc-host']}${ + ctx._requestHeaders['x-vercel-sc-basepath'] || '' + }` + if (this.debug) { + console.log('using cache endpoint', this.cacheEndpoint) + } + } else if (this.debug) { + console.log('no cache endpoint available') + } + if (ctx.maxMemoryCacheSize && !memoryCache) { + if (this.debug) { + console.log('using memory store for fetch cache') + } memoryCache = new LRUCache({ max: ctx.maxMemoryCacheSize, length({ value }) { @@ -32,29 +59,10 @@ export default class FetchCache implements CacheHandler { ) }, }) - } - this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE - this.headers = {} - this.headers['Content-Type'] = 'application/json' - - if (FETCH_CACHE_HEADER in ctx._requestHeaders) { - const newHeaders = JSON.parse( - ctx._requestHeaders[FETCH_CACHE_HEADER] as string - ) - for (const k in newHeaders) { - this.headers[k] = newHeaders[k] - } - delete ctx._requestHeaders[FETCH_CACHE_HEADER] - } - if (ctx._requestHeaders['x-vercel-sc-host']) { - this.cacheEndpoint = `https://${ctx._requestHeaders['x-vercel-sc-host']}${ - ctx._requestHeaders['x-vercel-sc-basepath'] || '' - }` + } else { if (this.debug) { - console.log('using cache endpoint', this.cacheEndpoint) + console.log('not using memory store for fetch cache') } - } else if (this.debug) { - console.log('no cache endpoint available') } } diff --git a/packages/next/src/server/lib/incremental-cache/index.ts b/packages/next/src/server/lib/incremental-cache/index.ts index 2c174f8dfea7..185c4873400e 100644 --- a/packages/next/src/server/lib/incremental-cache/index.ts +++ b/packages/next/src/server/lib/incremental-cache/index.ts @@ -265,7 +265,8 @@ export class IncrementalCache { // get data from cache if available async get( pathname: string, - fetchCache?: boolean + fetchCache?: boolean, + revalidate?: number ): Promise { // we don't leverage the prerender cache in dev mode // so that getStaticProps is always called for easier debugging @@ -281,7 +282,7 @@ export class IncrementalCache { const cacheData = await this.cacheHandler?.get(pathname, fetchCache) if (cacheData?.value?.kind === 'FETCH') { - const revalidate = cacheData.value.revalidate + revalidate = revalidate || cacheData.value.revalidate const age = Math.round( (Date.now() - (cacheData.lastModified || 0)) / 1000 ) diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index ec4d8e636486..5628636afb78 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -218,7 +218,8 @@ export function patchFetch({ if (cacheKey && staticGenerationStore?.incrementalCache) { const entry = await staticGenerationStore.incrementalCache.get( cacheKey, - true + true, + revalidate ) if (entry?.value && entry.value.kind === 'FETCH') { diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index 8cebd34607f6..da322eb276dd 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -50,6 +50,41 @@ createNextDescribe( }) } + it('should revalidate correctly with config and fetch revalidate', async () => { + const initial$ = await next.render$( + '/variable-config-revalidate/revalidate-3' + ) + const initialDate = initial$('#date').text() + const initialData = initial$('#data').text() + + expect(initialDate).toBeTruthy() + expect(initialData).toBeTruthy() + + let revalidatedDate + let revalidatedData + + // wait for a fresh revalidation + await check(async () => { + const $ = await next.render$('/variable-config-revalidate/revalidate-3') + + revalidatedDate = $('#date').text() + revalidatedData = $('#data').text() + + expect(revalidatedData).not.toBe(initialDate) + expect(revalidatedDate).not.toBe(initialData) + return 'success' + }, 'success') + + // the date should revalidate first after 3 seconds + // while the fetch data stays in place for 15 seconds + await check(async () => { + const $ = await next.render$('/variable-config-revalidate/revalidate-3') + expect($('#date').text()).not.toBe(revalidatedDate) + expect($('#data').text()).toBe(revalidatedData) + return 'success' + }, 'success') + }) + it('should include statusCode in cache', async () => { const $ = await next.render$('/variable-revalidate/status-code') const origData = JSON.parse($('#page-data').text()) @@ -163,6 +198,9 @@ createNextDescribe( 'ssr-forced/page.js', 'static-to-dynamic-error-forced/[id]/page.js', 'static-to-dynamic-error/[id]/page.js', + 'variable-config-revalidate/revalidate-3.html', + 'variable-config-revalidate/revalidate-3.rsc', + 'variable-config-revalidate/revalidate-3/page.js', 'variable-revalidate-edge/encoding/page.js', 'variable-revalidate-edge/no-store/page.js', 'variable-revalidate-edge/post-method-request/page.js', @@ -367,6 +405,11 @@ createNextDescribe( initialRevalidateSeconds: false, srcRoute: '/ssg-preview/[[...route]]', }, + '/variable-config-revalidate/revalidate-3': { + dataRoute: '/variable-config-revalidate/revalidate-3.rsc', + initialRevalidateSeconds: 3, + srcRoute: '/variable-config-revalidate/revalidate-3', + }, '/variable-revalidate/authorization': { dataRoute: '/variable-revalidate/authorization.rsc', initialRevalidateSeconds: 10, diff --git a/test/e2e/app-dir/app-static/app/variable-config-revalidate/revalidate-3/page.js b/test/e2e/app-dir/app-static/app/variable-config-revalidate/revalidate-3/page.js new file mode 100644 index 000000000000..ce19bd0ed95b --- /dev/null +++ b/test/e2e/app-dir/app-static/app/variable-config-revalidate/revalidate-3/page.js @@ -0,0 +1,24 @@ +export const revalidate = 3 + +export const metadata = { + title: 'Cache Test', +} + +export default async function Page() { + const data = await fetch( + 'https://next-data-api-endpoint.vercel.app/api/random', + { + next: { + revalidate: 15, + }, + } + ).then((res) => res.text()) + + return ( + <> +

/variable-config-revalidate/revalidate-3

+

{Date.now()}

+

{data}

+ + ) +}