diff --git a/packages/next/server/incremental-cache.ts b/packages/next/server/incremental-cache.ts index d015dd6ba41b0..32dd3bb45099a 100644 --- a/packages/next/server/incremental-cache.ts +++ b/packages/next/server/incremental-cache.ts @@ -9,15 +9,25 @@ function toRoute(pathname: string): string { return pathname.replace(/\/$/, '').replace(/\/index$/, '') || '/' } -type IncrementalCacheValue = { - html?: string - pageData?: any - isStale?: boolean - isNotFound?: boolean - isRedirect?: boolean +interface CachedRedirectValue { + kind: 'REDIRECT' + props: Object +} + +interface CachedPageValue { + kind: 'PAGE' + html: string + pageData: Object +} + +export type IncrementalCacheValue = CachedRedirectValue | CachedPageValue + +type IncrementalCacheEntry = { curRevalidate?: number | false // milliseconds to revalidate after revalidateAfter: number | false + isStale?: boolean + value: IncrementalCacheValue | null } export class IncrementalCache { @@ -29,7 +39,7 @@ export class IncrementalCache { } prerenderManifest: PrerenderManifest - cache: LRUCache + cache: LRUCache locales?: string[] constructor({ @@ -73,10 +83,10 @@ export class IncrementalCache { this.cache = new LRUCache({ // default to 50MB limit max: max || 50 * 1024 * 1024, - length(val) { - if (val.isNotFound || val.isRedirect) return 25 + length({ value }) { + if (!value || value.kind === 'REDIRECT') return 25 // rough estimate of size of cache value - return val.html!.length + JSON.stringify(val.pageData).length + return value.html.length + JSON.stringify(value.pageData).length }, }) } @@ -112,8 +122,8 @@ export class IncrementalCache { } // get data from cache if available - async get(pathname: string): Promise { - if (this.incrementalOptions.dev) return + async get(pathname: string): Promise { + if (this.incrementalOptions.dev) return null pathname = normalizePagePath(pathname) let data = this.cache.get(pathname) @@ -121,7 +131,7 @@ export class IncrementalCache { // let's check the disk for seed data if (!data) { if (this.prerenderManifest.notFoundRoutes.includes(pathname)) { - return { isNotFound: true, revalidateAfter: false } + return { revalidateAfter: false, value: null } } try { @@ -134,15 +144,21 @@ export class IncrementalCache { ) data = { - html, - pageData, revalidateAfter: this.calculateRevalidate(pathname), + value: { + kind: 'PAGE', + html, + pageData, + }, } this.cache.set(pathname, data) } catch (_) { // unable to get data from disk } } + if (!data) { + return null + } if ( data && @@ -164,12 +180,7 @@ export class IncrementalCache { // populate the incremental cache with new data async set( pathname: string, - data: { - html?: string - pageData?: any - isNotFound?: boolean - isRedirect?: boolean - }, + data: IncrementalCacheValue | null, revalidateSeconds?: number | false ) { if (this.incrementalOptions.dev) return @@ -188,17 +199,13 @@ export class IncrementalCache { pathname = normalizePagePath(pathname) this.cache.set(pathname, { - ...data, revalidateAfter: this.calculateRevalidate(pathname), + value: data, }) // TODO: This option needs to cease to exist unless it stops mutating the // `next build` output's manifest. - if ( - this.incrementalOptions.flushToDisk && - !data.isNotFound && - !data.isRedirect - ) { + if (this.incrementalOptions.flushToDisk && data?.kind === 'PAGE') { try { const seedPath = this.getSeedPath(pathname, 'html') await promises.mkdir(path.dirname(seedPath), { recursive: true }) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index aaf63c16a711f..481bfc793bb71 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -73,7 +73,7 @@ import prepareDestination, { } from '../shared/lib/router/utils/prepare-destination' import { sendPayload, setRevalidateHeaders } from './send-payload' import { serveStatic } from './serve-static' -import { IncrementalCache } from './incremental-cache' +import { IncrementalCache, IncrementalCacheValue } from './incremental-cache' import { execOnce } from '../shared/lib/utils' import { isBlockedPage } from './utils' import { loadEnvConfig } from '@next/env' @@ -1653,30 +1653,25 @@ export default class Server { } // Complete the response with cached data if its present - const cachedData = ssgCacheKey + const cacheEntry = ssgCacheKey ? await this.incrementalCache.get(ssgCacheKey) : undefined - if (cachedData) { - const data = isDataReq - ? JSON.stringify(cachedData.pageData) - : cachedData.html - + if (cacheEntry) { + const cachedData = cacheEntry.value const revalidateOptions = !this.renderOpts.dev ? { // When the page is 404 cache-control should not be added private: isPreviewMode || is404Page, stateful: false, // GSP response revalidate: - cachedData.curRevalidate !== undefined - ? cachedData.curRevalidate + cacheEntry.curRevalidate !== undefined + ? cacheEntry.curRevalidate : /* default to minimum revalidate (this should be an invariant) */ 1, } : undefined - if (!isDataReq && cachedData.pageData?.pageProps?.__N_REDIRECT) { - await handleRedirect(cachedData.pageData) - } else if (cachedData.isNotFound) { + if (!cachedData) { if (revalidateOptions) { setRevalidateHeaders(res, revalidateOptions) } @@ -1689,16 +1684,34 @@ export default class Server { query, } as UrlWithParsedQuery) } + } else if (cachedData.kind === 'REDIRECT') { + if (isDataReq) { + sendPayload( + req, + res, + JSON.stringify(cachedData.props), + 'json', + { + generateEtags: this.renderOpts.generateEtags, + poweredByHeader: this.renderOpts.poweredByHeader, + }, + revalidateOptions + ) + } else { + await handleRedirect(cachedData.props) + } } else { respondWith({ type: isDataReq ? 'json' : 'html', - body: data!, + body: isDataReq + ? JSON.stringify(cachedData.pageData) + : cachedData.html, revalidateOptions, }) } // Stop the request chain here if the data we sent was up-to-date - if (!cachedData.isStale) { + if (!cacheEntry.isStale) { return } } @@ -1912,11 +1925,15 @@ export default class Server { // Update the cache if the head request and cacheable if (isOrigin && ssgCacheKey) { - await this.incrementalCache.set( - ssgCacheKey, - { html: html!, pageData, isNotFound, isRedirect }, - sprRevalidate - ) + let cacheValue: IncrementalCacheValue | null + if (isNotFound) { + cacheValue = null + } else if (isRedirect) { + cacheValue = { kind: 'REDIRECT', props: pageData } + } else { + cacheValue = { kind: 'PAGE', html, pageData } + } + await this.incrementalCache.set(ssgCacheKey, cacheValue, sprRevalidate) } if (!hasResponded() && isNotFound) {