fix: only generate prefetch rsc for ppr enabled routes (#66395)

This adds details for every ISR cache request if the page being
requested supports PPR. If it does, it'll attempt to load the
`.prefetch.rsc` payload instead of the `.rsc` payload. This corrects a
bug that was present in deployed environments.

This additionally refactors the `isAppPPREnabled` out of most of the
application, as it's only used to determine if we should add to the
`prefetchDataRoute` fields in the `prerender-manifest.json`. To support
loading the prefetch file or not, we pass the `isRoutePPREnabled`
through with the cache get/set operations instead.

x-slack-ref:
https://vercel.slack.com/archives/C075MSFK9ML/p1717094328986429
This commit is contained in:
Wyatt Johnson 2024-06-03 12:55:05 -07:00 committed by GitHub
parent c5b39e31fd
commit be7d0c970e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 196 additions and 47 deletions

View file

@ -1820,7 +1820,6 @@ export default async function build(
minimalMode: ciEnvironment.hasNextSupport, minimalMode: ciEnvironment.hasNextSupport,
allowedRevalidateHeaderKeys: allowedRevalidateHeaderKeys:
config.experimental.allowedRevalidateHeaderKeys, config.experimental.allowedRevalidateHeaderKeys,
isAppPPREnabled,
}) })
incrementalCacheIpcPort = cacheInitialization.ipcPort incrementalCacheIpcPort = cacheInitialization.ipcPort
@ -2794,8 +2793,10 @@ export default async function build(
} }
let prefetchDataRoute: string | null | undefined let prefetchDataRoute: string | null | undefined
// We write the `.prefetch.rsc` when the app has PPR enabled, so // While we may only write the `.rsc` when the route does not
// always add the prefetch data route to the manifest. // have PPR enabled, we still want to generate the route when
// deployed so it doesn't 404. If the app has PPR enabled, we
// should add this key.
if (!isRouteHandler && isAppPPREnabled) { if (!isRouteHandler && isAppPPREnabled) {
prefetchDataRoute = path.posix.join( prefetchDataRoute = path.posix.join(
`${normalizedRoute}${RSC_PREFETCH_SUFFIX}` `${normalizedRoute}${RSC_PREFETCH_SUFFIX}`
@ -2868,8 +2869,10 @@ export default async function build(
let prefetchDataRoute: string | undefined let prefetchDataRoute: string | undefined
// We write the `.prefetch.rsc` when the app has PPR enabled, so // While we may only write the `.rsc` when the route does not
// always add the prefetch data route to the manifest. // have PPR enabled, we still want to generate the route when
// deployed so it doesn't 404. If the app has PPR enabled, we
// should add this key.
if (!isRouteHandler && isAppPPREnabled) { if (!isRouteHandler && isAppPPREnabled) {
prefetchDataRoute = path.posix.join( prefetchDataRoute = path.posix.join(
`${normalizedRoute}${RSC_PREFETCH_SUFFIX}` `${normalizedRoute}${RSC_PREFETCH_SUFFIX}`

View file

@ -1379,7 +1379,6 @@ export async function buildAppStaticPaths({
CurCacheHandler: CacheHandler, CurCacheHandler: CacheHandler,
requestHeaders, requestHeaders,
minimalMode: ciEnvironment.hasNextSupport, minimalMode: ciEnvironment.hasNextSupport,
isAppPPREnabled: false,
}) })
return StaticGenerationAsyncStorageWrapper.wrap( return StaticGenerationAsyncStorageWrapper.wrap(

View file

@ -14,7 +14,6 @@ export async function createIncrementalCache({
distDir, distDir,
dir, dir,
enabledDirectories, enabledDirectories,
isAppPPREnabled,
flushToDisk, flushToDisk,
}: { }: {
cacheHandler?: string cacheHandler?: string
@ -23,7 +22,6 @@ export async function createIncrementalCache({
distDir: string distDir: string
dir: string dir: string
enabledDirectories: NextEnabledDirectories enabledDirectories: NextEnabledDirectories
isAppPPREnabled: boolean
flushToDisk?: boolean flushToDisk?: boolean
}) { }) {
// Custom cache handler overrides. // Custom cache handler overrides.
@ -60,7 +58,6 @@ export async function createIncrementalCache({
serverDistDir: path.join(distDir, 'server'), serverDistDir: path.join(distDir, 'server'),
CurCacheHandler: CacheHandler, CurCacheHandler: CacheHandler,
minimalMode: hasNextSupport, minimalMode: hasNextSupport,
isAppPPREnabled,
}) })
;(globalThis as any).__incrementalCache = incrementalCache ;(globalThis as any).__incrementalCache = incrementalCache

View file

@ -55,7 +55,6 @@ import { validateRevalidate } from '../server/lib/patch-fetch'
import { TurborepoAccessTraceResult } from '../build/turborepo-access-trace' import { TurborepoAccessTraceResult } from '../build/turborepo-access-trace'
import { createProgress } from '../build/progress' import { createProgress } from '../build/progress'
import type { DeepReadonly } from '../shared/lib/deep-readonly' import type { DeepReadonly } from '../shared/lib/deep-readonly'
import { checkIsAppPPREnabled } from '../server/lib/experimental/ppr'
export class ExportError extends Error { export class ExportError extends Error {
code = 'NEXT_EXPORT_ERROR' code = 'NEXT_EXPORT_ERROR'
@ -358,7 +357,6 @@ export async function exportAppImpl(
strictNextHead: nextConfig.experimental.strictNextHead ?? true, strictNextHead: nextConfig.experimental.strictNextHead ?? true,
deploymentId: nextConfig.deploymentId, deploymentId: nextConfig.deploymentId,
experimental: { experimental: {
isAppPPREnabled: checkIsAppPPREnabled(nextConfig.experimental.ppr),
clientTraceMetadata: nextConfig.experimental.clientTraceMetadata, clientTraceMetadata: nextConfig.experimental.clientTraceMetadata,
swrDelta: nextConfig.swrDelta, swrDelta: nextConfig.swrDelta,
after: nextConfig.experimental.after ?? false, after: nextConfig.experimental.after ?? false,

View file

@ -95,7 +95,7 @@ export async function exportAppPage(
// instead of the standard rsc. This is because the standard rsc will // instead of the standard rsc. This is because the standard rsc will
// contain the dynamic data. We do this if any routes have PPR enabled so // contain the dynamic data. We do this if any routes have PPR enabled so
// that the cache read/write is the same. // that the cache read/write is the same.
else if (renderOpts.experimental.isAppPPREnabled) { else if (renderOpts.experimental.isRoutePPREnabled) {
// If PPR is enabled, we should emit the flight data as the prefetch // If PPR is enabled, we should emit the flight data as the prefetch
// payload. // payload.
await fileWriter( await fileWriter(

View file

@ -231,7 +231,6 @@ async function exportPageImpl(
distDir, distDir,
dir, dir,
enabledDirectories, enabledDirectories,
isAppPPREnabled: input.renderOpts.experimental.isAppPPREnabled,
// skip writing to disk in minimal mode for now, pending some // skip writing to disk in minimal mode for now, pending some
// changes to better support it // changes to better support it
flushToDisk: !hasNextSupport, flushToDisk: !hasNextSupport,

View file

@ -160,11 +160,6 @@ export interface RenderOptsPartial {
params?: ParsedUrlQuery params?: ParsedUrlQuery
isPrefetch?: boolean isPrefetch?: boolean
experimental: { experimental: {
/**
* When true, some routes support partial prerendering (PPR).
*/
isAppPPREnabled: boolean
/** /**
* When true, it indicates that the current page supports partial * When true, it indicates that the current page supports partial
* prerendering. * prerendering.

View file

@ -426,6 +426,8 @@ export default abstract class Server<
readonly data: NextDataPathnameNormalizer | undefined readonly data: NextDataPathnameNormalizer | undefined
} }
private readonly isAppPPREnabled: boolean
public constructor(options: ServerOptions) { public constructor(options: ServerOptions) {
const { const {
dir = '.', dir = '.',
@ -491,7 +493,7 @@ export default abstract class Server<
this.enabledDirectories = this.getEnabledDirectories(dev) this.enabledDirectories = this.getEnabledDirectories(dev)
const isAppPPREnabled = this.isAppPPREnabled =
this.enabledDirectories.app && this.enabledDirectories.app &&
checkIsAppPPREnabled(this.nextConfig.experimental.ppr) checkIsAppPPREnabled(this.nextConfig.experimental.ppr)
@ -500,7 +502,7 @@ export default abstract class Server<
// mode as otherwise that route is not exposed external to the server as // mode as otherwise that route is not exposed external to the server as
// we instead only rely on the headers. // we instead only rely on the headers.
postponed: postponed:
isAppPPREnabled && this.minimalMode this.isAppPPREnabled && this.minimalMode
? new PostponedPathnameNormalizer() ? new PostponedPathnameNormalizer()
: undefined, : undefined,
rsc: rsc:
@ -508,7 +510,7 @@ export default abstract class Server<
? new RSCPathnameNormalizer() ? new RSCPathnameNormalizer()
: undefined, : undefined,
prefetchRSC: prefetchRSC:
isAppPPREnabled && this.minimalMode this.isAppPPREnabled && this.minimalMode
? new PrefetchRSCPathnameNormalizer() ? new PrefetchRSCPathnameNormalizer()
: undefined, : undefined,
data: this.enabledDirectories.pages data: this.enabledDirectories.pages
@ -568,7 +570,6 @@ export default abstract class Server<
// @ts-expect-error internal field not publicly exposed // @ts-expect-error internal field not publicly exposed
isExperimentalCompile: this.nextConfig.experimental.isExperimentalCompile, isExperimentalCompile: this.nextConfig.experimental.isExperimentalCompile,
experimental: { experimental: {
isAppPPREnabled,
swrDelta: this.nextConfig.swrDelta, swrDelta: this.nextConfig.swrDelta,
clientTraceMetadata: this.nextConfig.experimental.clientTraceMetadata, clientTraceMetadata: this.nextConfig.experimental.clientTraceMetadata,
after: this.nextConfig.experimental.after ?? false, after: this.nextConfig.experimental.after ?? false,
@ -2009,9 +2010,9 @@ export default abstract class Server<
* enabled, then the given route _could_ support PPR. * enabled, then the given route _could_ support PPR.
*/ */
const couldSupportPPR: boolean = const couldSupportPPR: boolean =
this.isAppPPREnabled &&
typeof routeModule !== 'undefined' && typeof routeModule !== 'undefined' &&
isAppPageRouteModule(routeModule) && isAppPageRouteModule(routeModule)
this.renderOpts.experimental.isAppPPREnabled
// If this is a request that's rendering an app page that support's PPR, // If this is a request that's rendering an app page that support's PPR,
// then if we're in development mode (or using the experimental test // then if we're in development mode (or using the experimental test
@ -2772,6 +2773,7 @@ export default abstract class Server<
incrementalCache, incrementalCache,
isOnDemandRevalidate, isOnDemandRevalidate,
isPrefetch: req.headers.purpose === 'prefetch', isPrefetch: req.headers.purpose === 'prefetch',
isRoutePPREnabled,
} }
) )

View file

@ -19,12 +19,6 @@ type FileSystemCacheContext = Omit<
> & { > & {
fs: CacheFs fs: CacheFs
serverDistDir: string serverDistDir: string
/**
* isAppPPREnabled is true when PPR has been enabled either globally or just for
* some pages via the `incremental` option.
*/
isAppPPREnabled: boolean
} }
type TagsManifest = { type TagsManifest = {
@ -42,7 +36,6 @@ export default class FileSystemCache implements CacheHandler {
private pagesDir: boolean private pagesDir: boolean
private tagsManifestPath?: string private tagsManifestPath?: string
private revalidatedTags: string[] private revalidatedTags: string[]
private readonly isAppPPREnabled: boolean
private debug: boolean private debug: boolean
constructor(ctx: FileSystemCacheContext) { constructor(ctx: FileSystemCacheContext) {
@ -52,7 +45,6 @@ export default class FileSystemCache implements CacheHandler {
this.appDir = !!ctx._appDir this.appDir = !!ctx._appDir
this.pagesDir = !!ctx._pagesDir this.pagesDir = !!ctx._pagesDir
this.revalidatedTags = ctx.revalidatedTags this.revalidatedTags = ctx.revalidatedTags
this.isAppPPREnabled = ctx.isAppPPREnabled
this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE
if (ctx.maxMemoryCacheSize) { if (ctx.maxMemoryCacheSize) {
@ -177,7 +169,7 @@ export default class FileSystemCache implements CacheHandler {
public async get(...args: Parameters<CacheHandler['get']>) { public async get(...args: Parameters<CacheHandler['get']>) {
const [key, ctx = {}] = args const [key, ctx = {}] = args
const { tags, softTags, kindHint } = ctx const { tags, softTags, kindHint, isRoutePPREnabled } = ctx
let data = memoryCache?.get(key) let data = memoryCache?.get(key)
if (this.debug) { if (this.debug) {
@ -246,7 +238,10 @@ export default class FileSystemCache implements CacheHandler {
if (this.debug) { if (this.debug) {
console.log('tags vs storedTags mismatch', tags, storedTags) console.log('tags vs storedTags mismatch', tags, storedTags)
} }
await this.set(key, data.value, { tags }) await this.set(key, data.value, {
tags,
isRoutePPREnabled,
})
} }
} }
} else { } else {
@ -254,7 +249,7 @@ export default class FileSystemCache implements CacheHandler {
? await this.fs.readFile( ? await this.fs.readFile(
this.getFilePath( this.getFilePath(
`${key}${ `${key}${
this.isAppPPREnabled ? RSC_PREFETCH_SUFFIX : RSC_SUFFIX isRoutePPREnabled ? RSC_PREFETCH_SUFFIX : RSC_SUFFIX
}`, }`,
'app' 'app'
), ),
@ -398,7 +393,7 @@ export default class FileSystemCache implements CacheHandler {
this.getFilePath( this.getFilePath(
`${key}${ `${key}${
isAppPath isAppPath
? this.isAppPPREnabled ? ctx.isRoutePPREnabled
? RSC_PREFETCH_SUFFIX ? RSC_PREFETCH_SUFFIX
: RSC_SUFFIX : RSC_SUFFIX
: NEXT_DATA_SUFFIX : NEXT_DATA_SUFFIX

View file

@ -31,7 +31,6 @@ export interface CacheHandlerContext {
fetchCacheKeyPrefix?: string fetchCacheKeyPrefix?: string
prerenderManifest?: PrerenderManifest prerenderManifest?: PrerenderManifest
revalidatedTags: string[] revalidatedTags: string[]
isAppPPREnabled?: boolean
_appDir: boolean _appDir: boolean
_pagesDir: boolean _pagesDir: boolean
_requestHeaders: IncrementalCache['requestHeaders'] _requestHeaders: IncrementalCache['requestHeaders']
@ -104,7 +103,6 @@ export class IncrementalCache implements IncrementalCacheType {
fetchCacheKeyPrefix, fetchCacheKeyPrefix,
CurCacheHandler, CurCacheHandler,
allowedRevalidateHeaderKeys, allowedRevalidateHeaderKeys,
isAppPPREnabled,
}: { }: {
fs?: CacheFs fs?: CacheFs
dev: boolean dev: boolean
@ -121,7 +119,6 @@ export class IncrementalCache implements IncrementalCacheType {
getPrerenderManifest: () => DeepReadonly<PrerenderManifest> getPrerenderManifest: () => DeepReadonly<PrerenderManifest>
fetchCacheKeyPrefix?: string fetchCacheKeyPrefix?: string
CurCacheHandler?: typeof CacheHandler CurCacheHandler?: typeof CacheHandler
isAppPPREnabled: boolean
}) { }) {
const debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE const debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE
this.hasCustomCacheHandler = Boolean(CurCacheHandler) this.hasCustomCacheHandler = Boolean(CurCacheHandler)
@ -193,7 +190,6 @@ export class IncrementalCache implements IncrementalCacheType {
_appDir: !!appDir, _appDir: !!appDir,
_requestHeaders: requestHeaders, _requestHeaders: requestHeaders,
fetchCacheKeyPrefix, fetchCacheKeyPrefix,
isAppPPREnabled,
}) })
} }
} }
@ -434,6 +430,7 @@ export class IncrementalCache implements IncrementalCacheType {
fetchIdx?: number fetchIdx?: number
tags?: string[] tags?: string[]
softTags?: string[] softTags?: string[]
isRoutePPREnabled?: boolean
} = {} } = {}
): Promise<IncrementalCacheEntry | null> { ): Promise<IncrementalCacheEntry | null> {
if ( if (
@ -556,6 +553,7 @@ export class IncrementalCache implements IncrementalCacheType {
fetchUrl?: string fetchUrl?: string
fetchIdx?: number fetchIdx?: number
tags?: string[] tags?: string[]
isRoutePPREnabled?: boolean
} }
) { ) {
if ( if (

View file

@ -391,7 +391,6 @@ export default class NextNodeServer extends BaseServer<
!this.minimalMode && this.nextConfig.experimental.isrFlushToDisk, !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
getPrerenderManifest: () => this.getPrerenderManifest(), getPrerenderManifest: () => this.getPrerenderManifest(),
CurCacheHandler: CacheHandler, CurCacheHandler: CacheHandler,
isAppPPREnabled: this.renderOpts.experimental.isAppPPREnabled,
}) })
} }

View file

@ -53,6 +53,7 @@ export default class ResponseCache implements ResponseCacheBase {
isOnDemandRevalidate?: boolean isOnDemandRevalidate?: boolean
isPrefetch?: boolean isPrefetch?: boolean
incrementalCache: IncrementalCache incrementalCache: IncrementalCache
isRoutePPREnabled?: boolean
} }
): Promise<ResponseCacheEntry | null> { ): Promise<ResponseCacheEntry | null> {
// If there is no key for the cache, we can't possibly look this up in the // If there is no key for the cache, we can't possibly look this up in the
@ -89,7 +90,10 @@ export default class ResponseCache implements ResponseCacheBase {
let cachedResponse: IncrementalCacheItem = null let cachedResponse: IncrementalCacheItem = null
try { try {
cachedResponse = !this.minimalMode cachedResponse = !this.minimalMode
? await incrementalCache.get(key, { kindHint }) ? await incrementalCache.get(key, {
kindHint,
isRoutePPREnabled: context.isRoutePPREnabled,
})
: null : null
if (cachedResponse && !isOnDemandRevalidate) { if (cachedResponse && !isOnDemandRevalidate) {
@ -153,6 +157,7 @@ export default class ResponseCache implements ResponseCacheBase {
} else { } else {
await incrementalCache.set(key, resolveValue.value, { await incrementalCache.set(key, resolveValue.value, {
revalidate: resolveValue.revalidate, revalidate: resolveValue.revalidate,
isRoutePPREnabled: context.isRoutePPREnabled,
}) })
} }
} }
@ -167,6 +172,7 @@ export default class ResponseCache implements ResponseCacheBase {
Math.max(cachedResponse.revalidate || 3, 3), Math.max(cachedResponse.revalidate || 3, 3),
30 30
), ),
isRoutePPREnabled: context.isRoutePPREnabled,
}) })
} }

View file

@ -17,6 +17,8 @@ export interface ResponseCacheBase {
* provided it will test the filesystem to check. * provided it will test the filesystem to check.
*/ */
routeKind?: RouteKind routeKind?: RouteKind
isRoutePPREnabled?: boolean
} }
): Promise<ResponseCacheEntry | null> ): Promise<ResponseCacheEntry | null>
} }
@ -139,11 +141,16 @@ export interface IncrementalCache {
* determine the kind from the filesystem. * determine the kind from the filesystem.
*/ */
kindHint?: IncrementalCacheKindHint kindHint?: IncrementalCacheKindHint
isRoutePPREnabled?: boolean
} }
) => Promise<IncrementalCacheItem> ) => Promise<IncrementalCacheItem>
set: ( set: (
key: string, key: string,
data: IncrementalCacheValue | null, data: IncrementalCacheValue | null,
ctx: { revalidate: Revalidate } ctx: {
revalidate: Revalidate
isRoutePPREnabled?: boolean
}
) => Promise<void> ) => Promise<void>
} }

View file

@ -93,8 +93,6 @@ export default class NextWebServer extends BaseServer<
CurCacheHandler: CurCacheHandler:
this.serverOptions.webServerConfig.incrementalCacheHandler, this.serverOptions.webServerConfig.incrementalCacheHandler,
getPrerenderManifest: () => this.getPrerenderManifest(), getPrerenderManifest: () => this.getPrerenderManifest(),
// PPR is not supported in the Edge runtime.
isAppPPREnabled: false,
}) })
} }
protected getResponseCache() { protected getResponseCache() {

View file

@ -0,0 +1,7 @@
import { TestPage } from '../../../components/page'
export const experimental_ppr = true
export default function Page({ params: { locale } }) {
return <TestPage pathname={`/${locale}/about`} />
}

View file

@ -0,0 +1,13 @@
import { locales } from '../../components/page'
export async function generateStaticParams() {
return locales.map((locale) => ({ locale }))
}
export default function Layout({ children, params: { locale } }) {
return (
<html lang={locale}>
<body>{children}</body>
</html>
)
}

View file

@ -0,0 +1,5 @@
import { TestPage } from '../../components/page'
export default function Page({ params: { locale } }) {
return <TestPage pathname={`/${locale}`} />
}

View file

@ -0,0 +1,5 @@
import { TestPage } from '../../../components/page'
export default function Page({ params: { locale } }) {
return <TestPage pathname={`/${locale}/static`} noDynamic />
}

View file

@ -0,0 +1,3 @@
export default ({ children }) => {
return children
}

View file

@ -0,0 +1,7 @@
import { redirect } from 'next/navigation'
import { locales } from '../components/page'
export default () => {
// Redirect to the default locale
return redirect(`/${locales[0]}`)
}

View file

@ -0,0 +1,41 @@
import { unstable_noStore } from 'next/cache'
import Link from 'next/link'
import { Suspense } from 'react'
export const locales = ['en', 'fr']
export const links = [
{ href: '/', text: 'Home' },
...locales
.map((locale) => {
return [
{ href: `/${locale}`, text: locale },
{ href: `/${locale}/about`, text: `${locale} - About` },
{ href: `/${locale}/static`, text: `${locale} - Static` },
]
})
.flat(),
]
function Dynamic({ noDynamic = false }) {
if (!noDynamic) unstable_noStore()
return <div id="dynamic">Dynamic</div>
}
export function TestPage({ pathname, noDynamic = false }) {
return (
<div>
<ul>
{links.map(({ href, text }) => (
<li key={href}>
<Link href={href}>{text}</Link>
</li>
))}
</ul>
<code data-value={pathname}>{pathname}</code>
<Suspense fallback={<div>Loading...</div>}>
<Dynamic noDynamic={noDynamic} />
</Suspense>
</div>
)
}

View file

@ -0,0 +1,69 @@
import { NextInstance, nextTestSetup } from 'e2e-utils'
import { links, locales } from './components/page'
import glob from 'glob'
import { promisify } from 'node:util'
const globp = promisify(glob)
async function getDotNextFiles(next: NextInstance): Promise<Array<string>> {
const files = await globp('**/.next/**/*', {
cwd: next.testDir,
absolute: true,
})
return files
}
describe('ppr-navigations incremental', () => {
const { next, isNextDev, isTurbopack } = nextTestSetup({
files: __dirname,
})
it('can navigate between all the links and back without writing to disk', async () => {
const before = !isNextDev && !isTurbopack ? await getDotNextFiles(next) : []
const browser = await next.browser('/')
// Add a variable to the window so we can tell if it MPA navigated. If this
// value is still true at the end of the test, then we know that the page
// didn't MPA navigate.
const random = Math.random().toString(36).substring(7)
await browser.eval(`window.random = ${JSON.stringify(random)}`)
try {
for (const { href } of links) {
// Find the link element for the href and click it.
await browser.elementByCss(`a[href="${href}"]`).click()
// Wait for the network to be idle. This seems odd, but this is to help
// catch the condition where a rouge 404 triggers a MPA navigation.
await browser.waitForIdleNetwork()
// Check if the page navigated.
expect(await browser.eval(`window.random`)).toBe(random)
// Wait for that page to load.
if (href === '/') {
// The root page redirects to the first locale.
await browser.waitForElementByCss(`[data-value="/${locales[0]}"]`)
} else {
await browser.waitForElementByCss(`[data-value="${href}"]`)
}
await browser.elementByCss('#dynamic')
}
} finally {
await browser.close()
}
if (!isNextDev && !isTurbopack) {
const after = await getDotNextFiles(next)
// Ensure that no new files were written to disk. If this test fails, it's
// likely that there was a change to the incremental cache or file system
// cache that resulted in information like the ppr state not being properly
// propagated.
expect(after).toEqual(before)
}
})
})

View file

@ -0,0 +1,5 @@
module.exports = {
experimental: {
ppr: 'incremental',
},
}

View file

@ -15,7 +15,6 @@ describe('FileSystemCache', () => {
fs: nodeFs, fs: nodeFs,
serverDistDir: cacheDir, serverDistDir: cacheDir,
revalidatedTags: [], revalidatedTags: [],
isAppPPREnabled: false,
}) })
const binary = await fs.readFile( const binary = await fs.readFile(
@ -55,7 +54,6 @@ describe('FileSystemCache (isrMemory 0)', () => {
fs: nodeFs, fs: nodeFs,
serverDistDir: cacheDir, serverDistDir: cacheDir,
revalidatedTags: [], revalidatedTags: [],
isAppPPREnabled: false,
maxMemoryCacheSize: 0, // disable memory cache maxMemoryCacheSize: 0, // disable memory cache
}) })