Skip to content

Commit

Permalink
Ensure config and fetch revalidate are honored (#47255)
Browse files Browse the repository at this point in the history
This ensures revalidate can be fetch specific instead of cache key
specific and adds a test case to ensure config based revalidate isn't
overridden by fetch based revalidate.
  • Loading branch information
ijjk committed Mar 17, 2023
1 parent e601a3b commit ad223c8
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 24 deletions.
50 changes: 29 additions & 21 deletions packages/next/src/server/lib/incremental-cache/fetch-cache.ts
Expand Up @@ -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 }) {
Expand All @@ -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')
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/next/src/server/lib/incremental-cache/index.ts
Expand Up @@ -265,7 +265,8 @@ export class IncrementalCache {
// get data from cache if available
async get(
pathname: string,
fetchCache?: boolean
fetchCache?: boolean,
revalidate?: number
): Promise<IncrementalCacheEntry | null> {
// we don't leverage the prerender cache in dev mode
// so that getStaticProps is always called for easier debugging
Expand All @@ -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
)
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/server/lib/patch-fetch.ts
Expand Up @@ -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') {
Expand Down
43 changes: 43 additions & 0 deletions test/e2e/app-dir/app-static/app-static.test.ts
Expand Up @@ -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())
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand Down
@@ -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 (
<>
<p>/variable-config-revalidate/revalidate-3</p>
<p id="date">{Date.now()}</p>
<p id="data">{data}</p>
</>
)
}

0 comments on commit ad223c8

Please sign in to comment.