From 44613dc04599de61f63da24183eec22406e13ce0 Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Tue, 14 Nov 2023 23:23:29 +0000 Subject: [PATCH 1/2] v14.0.3-canary.8 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/font/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- packages/third-parties/package.json | 4 ++-- pnpm-lock.yaml | 16 ++++++++-------- 18 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lerna.json b/lerna.json index 6e2bacdd99839..3b3110817de4b 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "14.0.3-canary.7" + "version": "14.0.3-canary.8" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 89c5bc932dff2..21223390a31cb 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 7693fcaac3a6d..234b2f3a70236 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-config", "dependencies": { - "@next/eslint-plugin-next": "14.0.3-canary.7", + "@next/eslint-plugin-next": "14.0.3-canary.8", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 34244f3a64f92..6f006403585fb 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index e62b66e81a432..30c3be44542e5 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index f9f33874a7d44..6c7f8484a12ec 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 82d28298ccd15..089d3209570bd 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index b7a298ce63bae..63151c4f30794 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 7b1ea61900a36..272059f0e00ad 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index c6eb1961ceb81..25ff0894b6abe 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 2c459be4cb08b..7102b276b5944 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 6d6c890ae21e7..391282f5f7059 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 241d0a382d73b..4127d06dafb17 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index 0ad94bd0d7fb7..74908e462cd2a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -92,7 +92,7 @@ ] }, "dependencies": { - "@next/env": "14.0.3-canary.7", + "@next/env": "14.0.3-canary.8", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -146,11 +146,11 @@ "@mswjs/interceptors": "0.23.0", "@napi-rs/cli": "2.16.2", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "14.0.3-canary.7", - "@next/polyfill-nomodule": "14.0.3-canary.7", - "@next/react-dev-overlay": "14.0.3-canary.7", - "@next/react-refresh-utils": "14.0.3-canary.7", - "@next/swc": "14.0.3-canary.7", + "@next/polyfill-module": "14.0.3-canary.8", + "@next/polyfill-nomodule": "14.0.3-canary.8", + "@next/react-dev-overlay": "14.0.3-canary.8", + "@next/react-refresh-utils": "14.0.3-canary.8", + "@next/swc": "14.0.3-canary.8", "@opentelemetry/api": "1.6.0", "@playwright/test": "^1.35.1", "@taskr/clear": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 877233ee81bf8..062f8a83abd7d 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 48f16af1b6564..c7cc16b244083 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index e8bc8f20a2bec..1b8f85e8a09f4 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "14.0.3-canary.7", + "version": "14.0.3-canary.8", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -22,7 +22,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "14.0.3-canary.7", + "next": "14.0.3-canary.8", "outdent": "0.8.0", "prettier": "2.5.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5aef7876d556..2689909d7a14d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -735,7 +735,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.3.3 @@ -800,7 +800,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../next-env '@swc/helpers': specifier: 0.5.2 @@ -924,19 +924,19 @@ importers: specifier: 1.1.0 version: 1.1.0 '@next/polyfill-module': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../next-polyfill-nomodule '@next/react-dev-overlay': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../react-dev-overlay '@next/react-refresh-utils': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../react-refresh-utils '@next/swc': - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1587,7 +1587,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 14.0.3-canary.7 + specifier: 14.0.3-canary.8 version: link:../next outdent: specifier: 0.8.0 From 375d85d95e96df7be09f37eb620f977ae1cfd850 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 14 Nov 2023 17:20:35 -0700 Subject: [PATCH 2/2] fix: ensure batch values are sharable (#58315) Previously, the incremental cache returned objects with a readable stream (that were consumed). These can't be shared. Instead, this modifies the cache to instead return strings which are sharable. This also fixes a bug related to revalidation when writing an updated prefetch RSC payload to the filesystem when PPR is enabled. Previously it just wrote to `.rsc` and now it correctly writes a `.prefetch.rsc` file instead. --- packages/next/src/export/routes/app-page.ts | 3 +- packages/next/src/export/routes/app-route.ts | 10 ++- packages/next/src/export/routes/pages.ts | 7 +- packages/next/src/lib/constants.ts | 3 + packages/next/src/server/base-server.ts | 13 ++- .../incremental-cache/file-system-cache.ts | 23 ++++-- .../next/src/server/response-cache/index.ts | 81 +++++++++---------- .../next/src/server/response-cache/types.ts | 3 +- .../next/src/server/response-cache/utils.ts | 51 ++++++++++++ 9 files changed, 132 insertions(+), 62 deletions(-) create mode 100644 packages/next/src/server/response-cache/utils.ts diff --git a/packages/next/src/export/routes/app-page.ts b/packages/next/src/export/routes/app-page.ts index df3b97c3db47d..2806579353ba0 100644 --- a/packages/next/src/export/routes/app-page.ts +++ b/packages/next/src/export/routes/app-page.ts @@ -15,6 +15,7 @@ import { import { isDynamicUsageError } from '../helpers/is-dynamic-usage-error' import { NEXT_CACHE_TAGS_HEADER, + NEXT_META_SUFFIX, RSC_PREFETCH_SUFFIX, RSC_SUFFIX, } from '../../lib/constants' @@ -206,7 +207,7 @@ export async function exportAppPage( await fileWriter( ExportedAppPageFiles.META, - htmlFilepath.replace(/\.html$/, '.meta'), + htmlFilepath.replace(/\.html$/, NEXT_META_SUFFIX), JSON.stringify(meta, null, 2) ) diff --git a/packages/next/src/export/routes/app-route.ts b/packages/next/src/export/routes/app-route.ts index 9508a76418136..bbb2eeba645bb 100644 --- a/packages/next/src/export/routes/app-route.ts +++ b/packages/next/src/export/routes/app-route.ts @@ -4,7 +4,11 @@ import type { AppRouteRouteHandlerContext } from '../../server/future/route-modu import type { IncrementalCache } from '../../server/lib/incremental-cache' import { join } from 'path' -import { NEXT_CACHE_TAGS_HEADER } from '../../lib/constants' +import { + NEXT_BODY_SUFFIX, + NEXT_CACHE_TAGS_HEADER, + NEXT_META_SUFFIX, +} from '../../lib/constants' import { NodeNextRequest } from '../../server/base-http/node' import { RouteModuleLoader } from '../../server/future/helpers/module-loader/route-module-loader' import { @@ -104,7 +108,7 @@ export async function exportAppRoute( const body = Buffer.from(await blob.arrayBuffer()) await fileWriter( ExportedAppRouteFiles.BODY, - htmlFilepath.replace(/\.html$/, '.body'), + htmlFilepath.replace(/\.html$/, NEXT_BODY_SUFFIX), body, 'utf8' ) @@ -113,7 +117,7 @@ export async function exportAppRoute( const meta = { status: response.status, headers } await fileWriter( ExportedAppRouteFiles.META, - htmlFilepath.replace(/\.html$/, '.meta'), + htmlFilepath.replace(/\.html$/, NEXT_META_SUFFIX), JSON.stringify(meta) ) diff --git a/packages/next/src/export/routes/pages.ts b/packages/next/src/export/routes/pages.ts index 20f542d22ece3..0cedd3594696f 100644 --- a/packages/next/src/export/routes/pages.ts +++ b/packages/next/src/export/routes/pages.ts @@ -11,7 +11,10 @@ import type { MockedResponse, } from '../../server/lib/mock-request' import { isInAmpMode } from '../../shared/lib/amp-mode' -import { SERVER_PROPS_EXPORT_ERROR } from '../../lib/constants' +import { + NEXT_DATA_SUFFIX, + SERVER_PROPS_EXPORT_ERROR, +} from '../../lib/constants' import { NEXT_DYNAMIC_NO_SSR_CODE } from '../../shared/lib/lazy-dynamic/no-ssr-error' import AmpHtmlValidator from 'next/dist/compiled/amphtml-validator' import { FileType, fileExists } from '../../lib/file-exists' @@ -187,7 +190,7 @@ export async function exportPages( if (metadata.pageData) { const dataFile = join( pagesDataDir, - htmlFilename.replace(/\.html$/, '.json') + htmlFilename.replace(/\.html$/, NEXT_DATA_SUFFIX) ) await fileWriter( diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index 57d0632b772f2..10e1793f5d952 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -10,6 +10,9 @@ export const NEXT_DID_POSTPONE_HEADER = 'x-nextjs-postponed' export const RSC_PREFETCH_SUFFIX = '.prefetch.rsc' export const RSC_SUFFIX = '.rsc' +export const NEXT_DATA_SUFFIX = '.json' +export const NEXT_META_SUFFIX = '.meta' +export const NEXT_BODY_SUFFIX = '.body' export const NEXT_CACHE_TAGS_HEADER = 'x-next-cache-tags' export const NEXT_CACHE_SOFT_TAGS_HEADER = 'x-next-cache-soft-tags' diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index d987d92f586fc..e1d8e7960f67c 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -2453,10 +2453,9 @@ export default abstract class Server { ssgCacheKey, async ( hasResolved, - previousCacheEntry + previousCacheEntry, + isRevalidating ): Promise => { - // If this is a resume request, get the postponed. - const postponed = resumed ? resumed.postponed : undefined const isProduction = !this.renderOpts.dev const didRespond = hasResolved || res.sent @@ -2494,6 +2493,12 @@ export default abstract class Server { isOnDemandRevalidate = true } + // Only requests that aren't revalidating can be resumed. + const postponed = + !isOnDemandRevalidate && !isRevalidating && resumed + ? resumed.postponed + : undefined + // only allow on-demand revalidate for fallback: true/blocking // or for prerendered fallback: false paths if ( @@ -2603,7 +2608,7 @@ export default abstract class Server { { routeKind: routeModule?.definition.kind, incrementalCache, - isOnDemandRevalidate: isOnDemandRevalidate, + isOnDemandRevalidate, isPrefetch: req.headers.purpose === 'prefetch', } ) diff --git a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts index 33ab3d3a01d65..75cbc06b9ef35 100644 --- a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts @@ -10,6 +10,8 @@ import LRUCache from 'next/dist/compiled/lru-cache' import path from '../../../shared/lib/isomorphic/path' import { NEXT_CACHE_TAGS_HEADER, + NEXT_DATA_SUFFIX, + NEXT_META_SUFFIX, RSC_PREFETCH_SUFFIX, RSC_SUFFIX, } from '../../../lib/constants' @@ -140,7 +142,10 @@ export default class FileSystemCache implements CacheHandler { const { mtime } = await this.fs.stat(filePath) const meta = JSON.parse( - await this.fs.readFile(filePath.replace(/\.body$/, '.meta'), 'utf8') + await this.fs.readFile( + filePath.replace(/\.body$/, NEXT_META_SUFFIX), + 'utf8' + ) ) const cacheEntry: CacheHandlerValue = { @@ -204,7 +209,7 @@ export default class FileSystemCache implements CacheHandler { ) : JSON.parse( await this.fs.readFile( - this.getFilePath(`${key}.json`, 'pages'), + this.getFilePath(`${key}${NEXT_DATA_SUFFIX}`, 'pages'), 'utf8' ) ) @@ -215,7 +220,7 @@ export default class FileSystemCache implements CacheHandler { try { meta = JSON.parse( await this.fs.readFile( - filePath.replace(/\.html$/, '.meta'), + filePath.replace(/\.html$/, NEXT_META_SUFFIX), 'utf8' ) ) @@ -322,7 +327,7 @@ export default class FileSystemCache implements CacheHandler { } await this.fs.writeFile( - filePath.replace(/\.body$/, '.meta'), + filePath.replace(/\.body$/, NEXT_META_SUFFIX), JSON.stringify(meta, null, 2) ) return @@ -339,7 +344,13 @@ export default class FileSystemCache implements CacheHandler { await this.fs.writeFile( this.getFilePath( - `${key}.${isAppPath ? 'rsc' : 'json'}`, + `${key}${ + isAppPath + ? this.experimental.ppr + ? RSC_PREFETCH_SUFFIX + : RSC_SUFFIX + : NEXT_DATA_SUFFIX + }`, isAppPath ? 'app' : 'pages' ), isAppPath ? data.pageData : JSON.stringify(data.pageData) @@ -353,7 +364,7 @@ export default class FileSystemCache implements CacheHandler { } await this.fs.writeFile( - htmlPath.replace(/\.html$/, '.meta'), + htmlPath.replace(/\.html$/, NEXT_META_SUFFIX), JSON.stringify(meta) ) } diff --git a/packages/next/src/server/response-cache/index.ts b/packages/next/src/server/response-cache/index.ts index 76fa0ddad245f..5b0d30a5eac31 100644 --- a/packages/next/src/server/response-cache/index.ts +++ b/packages/next/src/server/response-cache/index.ts @@ -8,16 +8,16 @@ import type { } from './types' import { RouteKind } from '../future/route-kind' -import RenderResult from '../render-result' import { Batcher } from '../../lib/batcher' import { scheduleOnNextTick } from '../../lib/scheduler' +import { fromResponseCacheEntry, toResponseCacheEntry } from './utils' export * from './types' export default class ResponseCache implements ResponseCacheBase { private readonly batcher = Batcher.create< { key: string; isOnDemandRevalidate: boolean }, - ResponseCacheEntry | null, + IncrementalCacheItem | null, string >({ // Ensure on-demand revalidate doesn't block normal requests, it should be @@ -32,7 +32,7 @@ export default class ResponseCache implements ResponseCacheBase { private previousCacheItem?: { key: string - entry: ResponseCacheEntry | null + entry: IncrementalCacheItem | null expiresAt: number } @@ -45,7 +45,7 @@ export default class ResponseCache implements ResponseCacheBase { this[minimalModeKey] = minimalMode } - public get( + public async get( key: string | null, responseGenerator: ResponseGenerator, context: { @@ -61,7 +61,7 @@ export default class ResponseCache implements ResponseCacheBase { const { incrementalCache, isOnDemandRevalidate = false } = context - return this.batcher.batch( + const response = await this.batcher.batch( { key, isOnDemandRevalidate }, async (cacheKey, resolve) => { // We keep the previous cache entry around to leverage when the @@ -100,19 +100,8 @@ export default class ResponseCache implements ResponseCacheBase { } resolve({ - isStale: cachedResponse.isStale, + ...cachedResponse, revalidate: cachedResponse.curRevalidate, - value: - cachedResponse.value?.kind === 'PAGE' - ? { - kind: 'PAGE', - html: RenderResult.fromStatic(cachedResponse.value.html), - pageData: cachedResponse.value.pageData, - postponed: cachedResponse.value.postponed, - headers: cachedResponse.value.headers, - status: cachedResponse.value.status, - } - : cachedResponse.value, }) resolved = true @@ -123,14 +112,29 @@ export default class ResponseCache implements ResponseCacheBase { } } - const cacheEntry = await responseGenerator(resolved, cachedResponse) - const resolveValue = - cacheEntry === null - ? null - : { - ...cacheEntry, - isMiss: !cachedResponse, - } + const cacheEntry = await responseGenerator( + resolved, + cachedResponse, + true + ) + + // If the cache entry couldn't be generated, we don't want to cache + // the result. + if (!cacheEntry) { + // Unset the previous cache item if it was set. + if (this.minimalMode) this.previousCacheItem = undefined + return null + } + + const resolveValue = await fromResponseCacheEntry({ + ...cacheEntry, + isMiss: !cachedResponse, + }) + if (!resolveValue) { + // Unset the previous cache item if it was set. + if (this.minimalMode) this.previousCacheItem = undefined + return null + } // For on-demand revalidate wait to resolve until cache is set. // Otherwise resolve now. @@ -139,33 +143,18 @@ export default class ResponseCache implements ResponseCacheBase { resolved = true } - if (cacheEntry && typeof cacheEntry.revalidate !== 'undefined') { + if (typeof resolveValue.revalidate !== 'undefined') { if (this.minimalMode) { this.previousCacheItem = { key: cacheKey, - entry: cacheEntry, + entry: resolveValue, expiresAt: Date.now() + 1000, } } else { - await incrementalCache.set( - key, - cacheEntry.value?.kind === 'PAGE' - ? { - kind: 'PAGE', - html: cacheEntry.value.html.toUnchunkedString(), - postponed: cacheEntry.value.postponed, - pageData: cacheEntry.value.pageData, - headers: cacheEntry.value.headers, - status: cacheEntry.value.status, - } - : cacheEntry.value, - { - revalidate: cacheEntry.revalidate, - } - ) + await incrementalCache.set(key, resolveValue.value, { + revalidate: resolveValue.revalidate, + }) } - } else { - this.previousCacheItem = undefined } return resolveValue @@ -193,5 +182,7 @@ export default class ResponseCache implements ResponseCacheBase { } } ) + + return toResponseCacheEntry(response) } } diff --git a/packages/next/src/server/response-cache/types.ts b/packages/next/src/server/response-cache/types.ts index 7d76a993f1f97..815371e46d293 100644 --- a/packages/next/src/server/response-cache/types.ts +++ b/packages/next/src/server/response-cache/types.ts @@ -115,7 +115,8 @@ export type ResponseCacheEntry = { */ export type ResponseGenerator = ( hasResolved: boolean, - previousCacheEntry?: IncrementalCacheItem + previousCacheEntry?: IncrementalCacheItem, + isRevalidating?: boolean ) => Promise export type IncrementalCacheItem = { diff --git a/packages/next/src/server/response-cache/utils.ts b/packages/next/src/server/response-cache/utils.ts new file mode 100644 index 0000000000000..174d6fa619a4a --- /dev/null +++ b/packages/next/src/server/response-cache/utils.ts @@ -0,0 +1,51 @@ +import type { IncrementalCacheItem, ResponseCacheEntry } from './types' + +import RenderResult from '../render-result' + +export async function fromResponseCacheEntry( + cacheEntry: ResponseCacheEntry +): Promise { + return { + ...cacheEntry, + value: + cacheEntry.value?.kind === 'PAGE' + ? { + kind: 'PAGE', + html: await cacheEntry.value.html.toUnchunkedString(true), + postponed: cacheEntry.value.postponed, + pageData: cacheEntry.value.pageData, + headers: cacheEntry.value.headers, + status: cacheEntry.value.status, + } + : cacheEntry.value, + } +} + +export async function toResponseCacheEntry( + response: IncrementalCacheItem +): Promise { + if (!response) return null + + if (response.value?.kind === 'FETCH') { + throw new Error( + 'Invariant: unexpected cachedResponse of kind fetch in response cache' + ) + } + + return { + isMiss: response.isMiss, + isStale: response.isStale, + revalidate: response.revalidate, + value: + response.value?.kind === 'PAGE' + ? { + kind: 'PAGE', + html: RenderResult.fromStatic(response.value.html), + pageData: response.value.pageData, + postponed: response.value.postponed, + headers: response.value.headers, + status: response.value.status, + } + : response.value, + } +}