From 1f545aff055719c1bed404cd419822f2d49c3966 Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Tue, 21 May 2024 13:54:23 -0700 Subject: [PATCH] [Breaking] disable client router cache for page segments (#66039) This configures the default client router cache `staleTime.dynamic` value to be `0`. This means that: - Navigating between pages will always fire off a network request to get RSC data for the page segment, rather than restoring from router cache - Loading states will remain cached for 5 minutes (or whatever `config.experimental.staleTimes.static` is set to) - Shared layout data will continue to remain cached due to [partial rendering](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#4-partial-rendering) - Back/forward behavior will still restore from cache to ensure the browser can restore scroll position. It's possible to opt-out of this, and into the previous behavior, by setting the [`staleTimes`](https://nextjs.org/docs/app/api-reference/next-config-js/staleTimes) config in `next.config.js`: ```js /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { staleTimes: { dynamic: 30 }, }, } module.exports = nextConfig ``` --- .../04-caching/index.mdx | 2 +- .../05-next-config-js/staleTimes.mdx | 2 +- .../webpack/plugins/define-env-plugin.ts | 2 +- .../router-reducer/prefetch-cache-utils.ts | 2 +- packages/next/src/server/config-shared.ts | 2 +- .../client-cache.defaults.test.ts | 352 ++++++++++++++++++ ....test.ts => client-cache.original.test.ts} | 6 +- test/e2e/app-dir/app-prefetch/next.config.js | 1 - .../prefetching.stale-times.test.ts | 129 +++++++ .../app-dir/app-prefetch/prefetching.test.ts | 78 ---- test/e2e/app-dir/app/index.test.ts | 44 ++- test/ppr-tests-manifest.json | 13 +- test/turbopack-build-tests-manifest.json | 30 +- test/turbopack-dev-tests-manifest.json | 4 +- 14 files changed, 542 insertions(+), 125 deletions(-) create mode 100644 test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts rename test/e2e/app-dir/app-client-cache/{client-cache.test.ts => client-cache.original.test.ts} (98%) delete mode 100644 test/e2e/app-dir/app-prefetch/next.config.js create mode 100644 test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts diff --git a/docs/02-app/01-building-your-application/04-caching/index.mdx b/docs/02-app/01-building-your-application/04-caching/index.mdx index a3975e8ca9fcb..270c2ad5df6bb 100644 --- a/docs/02-app/01-building-your-application/04-caching/index.mdx +++ b/docs/02-app/01-building-your-application/04-caching/index.mdx @@ -364,7 +364,7 @@ The cache is stored in the browser's temporary memory. Two factors determine how - **Session**: The cache persists across navigation. However, it's cleared on page refresh. - **Automatic Invalidation Period**: The cache of an individual segment is automatically invalidated after a specific time. The duration depends on how the resource was [prefetched](/docs/app/api-reference/components/link#prefetch): - - **Default Prefetching** (`prefetch={null}` or unspecified): 30 seconds + - **Default Prefetching** (`prefetch={null}` or unspecified): 0 seconds - **Full Prefetching**: (`prefetch={true}` or `router.prefetch`): 5 minutes While a page refresh will clear **all** cached segments, the automatic invalidation period only affects the individual segment from the time it was prefetched. diff --git a/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx b/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx index b9136c8cac126..2dffd03e3a410 100644 --- a/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx +++ b/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx @@ -28,7 +28,7 @@ module.exports = nextConfig The `static` and `dynamic` properties correspond with the time period (in seconds) based on different types of [link prefetching](/docs/app/api-reference/components/link#prefetch). - The `dynamic` property is used when the `prefetch` prop on `Link` is left unspecified or is set to `false`. - - Default: 30 seconds + - Default: 0 seconds (not cached) - The `static` property is used when the `prefetch` prop on `Link` is set to `true`, or when calling [`router.prefetch`](/docs/app/building-your-application/caching#routerprefetch). - Default: 5 minutes diff --git a/packages/next/src/build/webpack/plugins/define-env-plugin.ts b/packages/next/src/build/webpack/plugins/define-env-plugin.ts index 79cb41dea7102..8e0f53f724373 100644 --- a/packages/next/src/build/webpack/plugins/define-env-plugin.ts +++ b/packages/next/src/build/webpack/plugins/define-env-plugin.ts @@ -178,7 +178,7 @@ export function getDefineEnv({ config.experimental.manualClientBasePath ?? false, 'process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME': JSON.stringify( isNaN(Number(config.experimental.staleTimes?.dynamic)) - ? 30 // 30 seconds + ? 0 : config.experimental.staleTimes?.dynamic ), 'process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME': JSON.stringify( diff --git a/packages/next/src/client/components/router-reducer/prefetch-cache-utils.ts b/packages/next/src/client/components/router-reducer/prefetch-cache-utils.ts index 76e43901475e0..93d98eb41b4de 100644 --- a/packages/next/src/client/components/router-reducer/prefetch-cache-utils.ts +++ b/packages/next/src/client/components/router-reducer/prefetch-cache-utils.ts @@ -239,7 +239,7 @@ export function prunePrefetchCache( } // These values are set by `define-env-plugin` (based on `nextConfig.experimental.staleTimes`) -// and default to 5 minutes (static) / 30 seconds (dynamic) +// and default to 5 minutes (static) / 0 seconds (dynamic) const DYNAMIC_STALETIME_MS = Number(process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME) * 1000 diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index c87d791c2a57f..3331a943047e5 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -964,7 +964,7 @@ export const defaultConfig: NextConfig = { optimizeServerReact: true, useEarlyImport: false, staleTimes: { - dynamic: 30, + dynamic: 0, static: 300, }, allowDevelopmentBuild: undefined, diff --git a/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts b/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts new file mode 100644 index 0000000000000..f8a168aca14c7 --- /dev/null +++ b/test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts @@ -0,0 +1,352 @@ +import { nextTestSetup } from 'e2e-utils' +import { check } from 'next-test-utils' +import { BrowserInterface } from 'next-webdriver' +import { + browserConfigWithFixedTime, + createRequestsListener, + fastForwardTo, + getPathname, +} from './test-utils' + +describe('app dir client cache semantics (default semantics)', () => { + const { next, isNextDev } = nextTestSetup({ + files: __dirname, + }) + + if (isNextDev) { + // dev doesn't support prefetch={true}, so this just performs a basic test to make sure data is reused for 30s + it('should return fresh data every navigation', async () => { + let browser = (await next.browser( + '/', + browserConfigWithFixedTime + )) as BrowserInterface + + // navigate to prefetch-auto page + await browser.elementByCss('[href="/1"]').click() + + let initialNumber = await browser.elementById('random-number').text() + + // Navigate back to the index, and then back to the prefetch-auto page + await browser.elementByCss('[href="/"]').click() + await browser.eval(fastForwardTo, 5 * 1000) + await browser.elementByCss('[href="/1"]').click() + + let newNumber = await browser.elementById('random-number').text() + + expect(newNumber).not.toBe(initialNumber) + }) + } else { + describe('prefetch={true}', () => { + let browser: BrowserInterface + + beforeEach(async () => { + browser = (await next.browser( + '/', + browserConfigWithFixedTime + )) as BrowserInterface + }) + + it('should prefetch the full page', async () => { + const { getRequests, clearRequests } = + await createRequestsListener(browser) + await check(() => { + return getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/0' && !didPartialPrefetch + ) + ? 'success' + : 'fail' + }, 'success') + + clearRequests() + + await browser + .elementByCss('[href="/0?timeout=0"]') + .click() + .waitForElementByCss('#random-number') + + expect( + getRequests().every(([url]) => getPathname(url) !== '/0') + ).toEqual(true) + }) + it('should re-use the cache for the full page, only for 5 mins', async () => { + const randomNumber = await browser + .elementByCss('[href="/0?timeout=0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + await browser.elementByCss('[href="/"]').click() + + const number = await browser + .elementByCss('[href="/0?timeout=0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(number).toBe(randomNumber) + + await browser.eval(fastForwardTo, 5 * 60 * 1000) + + await browser.elementByCss('[href="/"]').click() + + const newNumber = await browser + .elementByCss('[href="/0?timeout=0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(newNumber).not.toBe(randomNumber) + }) + + it('should prefetch again after 5 mins if the link is visible again', async () => { + const { getRequests, clearRequests } = + await createRequestsListener(browser) + + await check(() => { + return getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/0' && !didPartialPrefetch + ) + ? 'success' + : 'fail' + }, 'success') + + const randomNumber = await browser + .elementByCss('[href="/0?timeout=0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + await browser.eval(fastForwardTo, 5 * 60 * 1000) + clearRequests() + + await browser.elementByCss('[href="/"]').click() + + await check(() => { + return getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/0' && !didPartialPrefetch + ) + ? 'success' + : 'fail' + }, 'success') + + const number = await browser + .elementByCss('[href="/0?timeout=0"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(number).not.toBe(randomNumber) + }) + }) + describe('prefetch={false}', () => { + let browser: BrowserInterface + + beforeEach(async () => { + browser = (await next.browser( + '/', + browserConfigWithFixedTime + )) as BrowserInterface + }) + it('should not prefetch the page at all', async () => { + const { getRequests } = await createRequestsListener(browser) + + await browser + .elementByCss('[href="/2"]') + .click() + .waitForElementByCss('#random-number') + + expect( + getRequests().filter(([url]) => getPathname(url) === '/2') + ).toHaveLength(1) + + expect( + getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/2' && didPartialPrefetch + ) + ).toBe(false) + }) + it('should not re-use the page segment cache', async () => { + const randomNumber = await browser + .elementByCss('[href="/2"]') + .click() + .waitForElementByCss('#random-number') + .text() + + await browser.elementByCss('[href="/"]').click() + + const number = await browser + .elementByCss('[href="/2"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(number).not.toBe(randomNumber) + + await browser.eval(fastForwardTo, 30 * 1000) + + await browser.elementByCss('[href="/"]').click() + + const newNumber = await browser + .elementByCss('[href="/2"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(newNumber).not.toBe(randomNumber) + }) + }) + describe('prefetch={undefined} - default', () => { + let browser: BrowserInterface + + beforeEach(async () => { + browser = (await next.browser( + '/', + browserConfigWithFixedTime + )) as BrowserInterface + }) + + it('should prefetch partially a dynamic page', async () => { + const { getRequests, clearRequests } = + await createRequestsListener(browser) + + await check(() => { + return getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/1' && didPartialPrefetch + ) + ? 'success' + : 'fail' + }, 'success') + + clearRequests() + + await browser + .elementByCss('[href="/1"]') + .click() + .waitForElementByCss('#random-number') + + expect( + getRequests().some( + ([url, didPartialPrefetch]) => + getPathname(url) === '/1' && !didPartialPrefetch + ) + ).toBe(true) + }) + it('should not re-use the page segment cache', async () => { + const randomNumber = await browser + .elementByCss('[href="/1"]') + .click() + .waitForElementByCss('#random-number') + .text() + + await browser.elementByCss('[href="/"]').click() + + const number = await browser + .elementByCss('[href="/1"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(number).not.toBe(randomNumber) + + await browser.eval(fastForwardTo, 5 * 1000) + + await browser.elementByCss('[href="/"]').click() + + const newNumber = await browser + .elementByCss('[href="/1"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(newNumber).not.toBe(randomNumber) + + await browser.eval(fastForwardTo, 30 * 1000) + + await browser.elementByCss('[href="/"]').click() + + const newNumber2 = await browser + .elementByCss('[href="/1"]') + .click() + .waitForElementByCss('#random-number') + .text() + + expect(newNumber2).not.toBe(newNumber) + }) + + it('should refetch the full page after 5 mins', async () => { + const randomLoadingNumber = await browser + .elementByCss('[href="/1?timeout=1000"]') + .click() + .waitForElementByCss('#loading') + .text() + + const randomNumber = await browser + .waitForElementByCss('#random-number') + .text() + + await browser.eval(fastForwardTo, 5 * 60 * 1000) + + await browser + .elementByCss('[href="/"]') + .click() + .waitForElementByCss('[href="/1?timeout=1000"]') + + const newLoadingNumber = await browser + .elementByCss('[href="/1?timeout=1000"]') + .click() + .waitForElementByCss('#loading') + .text() + + const newNumber = await browser + .waitForElementByCss('#random-number') + .text() + + expect(newLoadingNumber).not.toBe(randomLoadingNumber) + + expect(newNumber).not.toBe(randomNumber) + }) + + it('should respect a loading boundary that returns `null`', async () => { + await browser.elementByCss('[href="/null-loading"]').click() + + // the page content should disappear immediately + expect( + await browser.hasElementByCssSelector('[href="/null-loading"]') + ).toBeFalse() + + // the root layout should still be visible + expect(await browser.hasElementByCssSelector('#root-layout')).toBeTrue() + + // the dynamic content should eventually appear + await browser.waitForElementByCss('#random-number') + expect( + await browser.hasElementByCssSelector('#random-number') + ).toBeTrue() + }) + }) + + it('should renew the initial seeded data after expiration time', async () => { + const browser = (await next.browser( + '/without-loading/1', + browserConfigWithFixedTime + )) as BrowserInterface + + const initialNumber = await browser.elementById('random-number').text() + + // Expire the cache + await browser.eval(fastForwardTo, 30 * 1000) + await browser.elementByCss('[href="/without-loading"]').click() + await browser.elementByCss('[href="/without-loading/1"]').click() + + const newNumber = await browser.elementById('random-number').text() + + // The number should be different, as the seeded data has expired after 30s + expect(newNumber).not.toBe(initialNumber) + }) + } +}) diff --git a/test/e2e/app-dir/app-client-cache/client-cache.test.ts b/test/e2e/app-dir/app-client-cache/client-cache.original.test.ts similarity index 98% rename from test/e2e/app-dir/app-client-cache/client-cache.test.ts rename to test/e2e/app-dir/app-client-cache/client-cache.original.test.ts index 8b8404671217a..f5065702d6466 100644 --- a/test/e2e/app-dir/app-client-cache/client-cache.test.ts +++ b/test/e2e/app-dir/app-client-cache/client-cache.original.test.ts @@ -8,9 +8,13 @@ import { getPathname, } from './test-utils' -describe('app dir client cache semantics', () => { +// This preserves existing tests for the 30s/5min heuristic (previous router defaults) +describe('app dir client cache semantics (30s/5min)', () => { const { next, isNextDev } = nextTestSetup({ files: __dirname, + nextConfig: { + experimental: { staleTimes: { dynamic: 30, static: 180 } }, + }, }) if (isNextDev) { diff --git a/test/e2e/app-dir/app-prefetch/next.config.js b/test/e2e/app-dir/app-prefetch/next.config.js deleted file mode 100644 index 4ba52ba2c8df6..0000000000000 --- a/test/e2e/app-dir/app-prefetch/next.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts b/test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts new file mode 100644 index 0000000000000..08286b26b98fc --- /dev/null +++ b/test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts @@ -0,0 +1,129 @@ +import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' + +import { NEXT_RSC_UNION_QUERY } from 'next/dist/client/components/app-router-headers' + +const browserConfigWithFixedTime = { + beforePageLoad: (page) => { + page.addInitScript(() => { + const startTime = new Date() + const fixedTime = new Date('2023-04-17T00:00:00Z') + + // Override the Date constructor + // @ts-ignore + // eslint-disable-next-line no-native-reassign + Date = class extends Date { + constructor() { + super() + // @ts-ignore + return new startTime.constructor(fixedTime) + } + + static now() { + return fixedTime.getTime() + } + } + }) + }, +} + +describe('app dir - prefetching (custom staleTime)', () => { + const { next, isNextDev } = nextTestSetup({ + files: __dirname, + skipDeployment: true, + nextConfig: { + experimental: { + staleTimes: { + static: 180, + dynamic: 30, + }, + }, + }, + }) + + if (isNextDev) { + it('should skip next dev for now', () => {}) + return + } + + it('should not fetch again when a static page was prefetched when navigating to it twice', async () => { + const browser = await next.browser('/404', browserConfigWithFixedTime) + let requests: string[] = [] + + browser.on('request', (req) => { + requests.push(new URL(req.url()).pathname) + }) + await browser.eval('location.href = "/"') + + await browser.eval( + `window.nd.router.prefetch("/static-page", {kind: "auto"})` + ) + + await retry(async () => { + expect( + requests.filter( + (request) => + request === '/static-page' || request.includes(NEXT_RSC_UNION_QUERY) + ).length + ).toBe(1) + }) + + await browser + .elementByCss('#to-static-page') + .click() + .waitForElementByCss('#static-page') + + await browser + .elementByCss('#to-home') + // Go back to home page + .click() + // Wait for homepage to load + .waitForElementByCss('#to-static-page') + // Click on the link to the static page again + .click() + // Wait for the static page to load again + .waitForElementByCss('#static-page') + + await retry(async () => { + expect( + requests.filter( + (request) => + request === '/static-page' || request.includes(NEXT_RSC_UNION_QUERY) + ).length + ).toBe(1) + }) + }) + + it('should not re-fetch cached data when navigating back to a route group', async () => { + const browser = await next.browser('/prefetch-auto-route-groups') + // once the page has loaded, we expect a data fetch + expect(await browser.elementById('count').text()).toBe('1') + + // once navigating to a sub-page, we expect another data fetch + await browser + .elementByCss("[href='/prefetch-auto-route-groups/sub/foo']") + .click() + + // navigating back to the route group page shouldn't trigger any data fetch + await browser.elementByCss("[href='/prefetch-auto-route-groups']").click() + + // confirm that the dashboard page is still rendering the stale fetch count, as it should be cached + expect(await browser.elementById('count').text()).toBe('1') + + // navigating to a new sub-page, we expect another data fetch + await browser + .elementByCss("[href='/prefetch-auto-route-groups/sub/bar']") + .click() + + // finally, going back to the route group page shouldn't trigger any data fetch + await browser.elementByCss("[href='/prefetch-auto-route-groups']").click() + + // confirm that the dashboard page is still rendering the stale fetch count, as it should be cached + expect(await browser.elementById('count').text()).toBe('1') + + await browser.refresh() + // reloading the page, we should now get an accurate total number of fetches + // the initial fetch, 2 sub-page fetches, and a final fetch when reloading the page + expect(await browser.elementById('count').text()).toBe('4') + }) +}) diff --git a/test/e2e/app-dir/app-prefetch/prefetching.test.ts b/test/e2e/app-dir/app-prefetch/prefetching.test.ts index 83fcd3c51a70a..002836bc969f4 100644 --- a/test/e2e/app-dir/app-prefetch/prefetching.test.ts +++ b/test/e2e/app-dir/app-prefetch/prefetching.test.ts @@ -114,51 +114,6 @@ describe('app dir - prefetching', () => { ).toBe(1) }) - it('should not fetch again when a static page was prefetched when navigating to it twice', async () => { - const browser = await next.browser('/404', browserConfigWithFixedTime) - let requests: string[] = [] - - browser.on('request', (req) => { - requests.push(new URL(req.url()).pathname) - }) - await browser.eval('location.href = "/"') - - await browser.eval( - `window.nd.router.prefetch("/static-page", {kind: "auto"})` - ) - await check(() => { - return requests.some( - (req) => - req.includes('static-page') && !req.includes(NEXT_RSC_UNION_QUERY) - ) - ? 'success' - : JSON.stringify(requests) - }, 'success') - - await browser - .elementByCss('#to-static-page') - .click() - .waitForElementByCss('#static-page') - - await browser - .elementByCss('#to-home') - // Go back to home page - .click() - // Wait for homepage to load - .waitForElementByCss('#to-static-page') - // Click on the link to the static page again - .click() - // Wait for the static page to load again - .waitForElementByCss('#static-page') - - expect( - requests.filter( - (request) => - request === '/static-page' || request.includes(NEXT_RSC_UNION_QUERY) - ).length - ).toBe(1) - }) - it('should calculate `_rsc` query based on `Next-Url`', async () => { const browser = await next.browser('/404', browserConfigWithFixedTime) let staticPageRequests: string[] = [] @@ -373,38 +328,5 @@ describe('app dir - prefetching', () => { ) }) }) - - it('should not re-fetch cached data when navigating back to a route group', async () => { - const browser = await next.browser('/prefetch-auto-route-groups') - // once the page has loaded, we expect a data fetch - expect(await browser.elementById('count').text()).toBe('1') - - // once navigating to a sub-page, we expect another data fetch - await browser - .elementByCss("[href='/prefetch-auto-route-groups/sub/foo']") - .click() - - // navigating back to the route group page shouldn't trigger any data fetch - await browser.elementByCss("[href='/prefetch-auto-route-groups']").click() - - // confirm that the dashboard page is still rendering the stale fetch count, as it should be cached - expect(await browser.elementById('count').text()).toBe('1') - - // navigating to a new sub-page, we expect another data fetch - await browser - .elementByCss("[href='/prefetch-auto-route-groups/sub/bar']") - .click() - - // finally, going back to the route group page shouldn't trigger any data fetch - await browser.elementByCss("[href='/prefetch-auto-route-groups']").click() - - // confirm that the dashboard page is still rendering the stale fetch count, as it should be cached - expect(await browser.elementById('count').text()).toBe('1') - - await browser.refresh() - // reloading the page, we should now get an accurate total number of fetches - // the initial fetch, 2 sub-page fetches, and a final fetch when reloading the page - expect(await browser.elementById('count').text()).toBe('4') - }) }) }) diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index 63762c179520d..485709ac3fa55 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -666,10 +666,12 @@ describe('app dir - basic', () => { } }) - // TODO-APP: Re-enable this test. it('should soft push', async () => { const browser = await next.browser('/link-soft-push') + // set a flag once the page loads so we can track if a hard nav occurred (which would reset the flag) + await browser.eval('window.__nextSoftPushTest = 1') + try { // Click the link on the page, and verify that the history entry was // added. @@ -685,26 +687,25 @@ describe('app dir - basic', () => { await browser.back() await browser.elementById('link').click() - // Get the date again, and compare, they should be the same. + // Get the ID again, and compare, they should be the same. const secondID = await browser.elementById('render-id').text() - if (isPPREnabledByDefault) { - // TODO: Investigate why this fails when PPR is enabled. It doesn't - // always fail, though, so we should also fix the flakiness of - // the test. - } else { - // This is the correct behavior. - expect(firstID).toBe(secondID) - } + // router cache should have invalidated the page content, so the IDs should be different + expect(firstID).not.toBe(secondID) + + // verify that the flag is still set + expect(await browser.eval('window.__nextSoftPushTest')).toBe(1) } finally { await browser.close() } }) - // TODO-APP: investigate this test - it.skip('should soft replace', async () => { + it('should soft replace', async () => { const browser = await next.browser('/link-soft-replace') + // set a flag once the page loads so we can track if a hard nav occurred (which would reset the flag) + await browser.eval('window.__nextSoftPushTest = 1') + try { // Get the render ID so we can compare it. const firstID = await browser.elementById('render-id').text() @@ -713,12 +714,14 @@ describe('app dir - basic', () => { // added. expect(await browser.eval('window.history.length')).toBe(2) await browser.elementById('self-link').click() - await browser.waitForElementByCss('#render-id') - expect(await browser.eval('window.history.length')).toBe(2) - // Get the id on the rendered page. - const secondID = await browser.elementById('render-id').text() - expect(secondID).toBe(firstID) + await retry(async () => { + // Get the id on the rendered page. + const secondID = await browser.elementById('render-id').text() + expect(secondID).not.toBe(firstID) + + expect(await browser.eval('window.history.length')).toBe(2) + }) // Navigate to the subpage, verify that the history entry was NOT added. await browser.elementById('subpage-link').click() @@ -730,9 +733,12 @@ describe('app dir - basic', () => { await browser.waitForElementByCss('#render-id') expect(await browser.eval('window.history.length')).toBe(2) - // Get the date again, and compare, they should be the same. + // Get the ID again, and compare, they should be the same. const thirdID = await browser.elementById('render-id').text() - expect(thirdID).toBe(firstID) + expect(thirdID).not.toBe(firstID) + + // verify that the flag is still set + expect(await browser.eval('window.__nextSoftPushTest')).toBe(1) } finally { await browser.close() } diff --git a/test/ppr-tests-manifest.json b/test/ppr-tests-manifest.json index f9429ddda79fb..d3f170e5f5ea3 100644 --- a/test/ppr-tests-manifest.json +++ b/test/ppr-tests-manifest.json @@ -9,11 +9,16 @@ "app-dir static/dynamic handling should output debug info for static bailouts" ] }, - "test/e2e/app-dir/app-client-cache/client-cache.test.ts": { + "test/e2e/app-dir/app-client-cache/client-cache.original.test.ts": { "failed": [ - "app dir client cache semantics prefetch={undefined} - default should re-use the full cache for only 30 seconds", - "app dir client cache semantics prefetch={undefined} - default should renew the 30s cache once the data is revalidated", - "app dir client cache semantics prefetch={undefined} - default should refetch the full page after 5 mins" + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should re-use the full cache for only 30 seconds", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should renew the 30s cache once the data is revalidated", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should refetch the full page after 5 mins" + ] + }, + "test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts": { + "failed": [ + "app dir client cache semantics (default semantics) prefetch={undefined} - default should refetch the full page after 5 mins" ] }, "test/e2e/app-dir/headers-static-bailout/headers-static-bailout.test.ts": { diff --git a/test/turbopack-build-tests-manifest.json b/test/turbopack-build-tests-manifest.json index ac106fc857fd2..91c046b46c816 100644 --- a/test/turbopack-build-tests-manifest.json +++ b/test/turbopack-build-tests-manifest.json @@ -274,21 +274,21 @@ "flakey": [], "runtimeError": false }, - "test/e2e/app-dir/app-client-cache/client-cache.test.ts": { + "test/e2e/app-dir/app-client-cache/client-cache.original.test.ts": { "passed": [ - "app dir client cache semantics prefetch={false} should not prefetch the page at all", - "app dir client cache semantics prefetch={false} should re-use the cache only for 30 seconds", - "app dir client cache semantics prefetch={true} should prefetch again after 5 mins if the link is visible again", - "app dir client cache semantics prefetch={true} should prefetch the full page", - "app dir client cache semantics prefetch={true} should re-use the cache for the full page, only for 5 mins", - "app dir client cache semantics prefetch={undefined} - default should prefetch partially a dynamic page", - "app dir client cache semantics prefetch={undefined} - default should re-use the full cache for only 30 seconds", - "app dir client cache semantics prefetch={undefined} - default should refetch below the fold after 30 seconds", - "app dir client cache semantics prefetch={undefined} - default should refetch the full page after 5 mins", - "app dir client cache semantics prefetch={undefined} - default should renew the 30s cache once the data is revalidated", - "app dir client cache semantics prefetch={undefined} - default should respect a loading boundary that returns `null`", - "app dir client cache semantics should renew the initial seeded data after expiration time", - "app dir client cache semantics should seed the prefetch cache with the fetched page data" + "app dir client cache semantics (30s/5min) prefetch={false} should not prefetch the page at all", + "app dir client cache semantics (30s/5min) prefetch={false} should re-use the cache only for 30 seconds", + "app dir client cache semantics (30s/5min) prefetch={true} should prefetch again after 5 mins if the link is visible again", + "app dir client cache semantics (30s/5min) prefetch={true} should prefetch the full page", + "app dir client cache semantics (30s/5min) prefetch={true} should re-use the cache for the full page, only for 5 mins", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should prefetch partially a dynamic page", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should re-use the full cache for only 30 seconds", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should refetch below the fold after 30 seconds", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should refetch the full page after 5 mins", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should renew the 30s cache once the data is revalidated", + "app dir client cache semantics (30s/5min) prefetch={undefined} - default should respect a loading boundary that returns `null`", + "app dir client cache semantics (30s/5min) should renew the initial seeded data after expiration time", + "app dir client cache semantics (30s/5min) should seed the prefetch cache with the fetched page data" ], "failed": [], "pending": [], @@ -1076,6 +1076,7 @@ "app dir - basic should push to external url", "app dir - basic should replace to external url", "app dir - basic should soft push", + "app dir - basic should soft replace", "app dir - basic bootstrap scripts should only bootstrap with one script, prinitializing the rest", "app dir - basic bootstrap scripts should successfully bootstrap even when using CSP", "app dir - basic data fetch with response over 16KB with chunked encoding should load page when fetching a large amount of data", @@ -1173,7 +1174,6 @@ "app dir - basic template component should render the template that is a server component and rerender on navigation" ], "pending": [ - "app dir - basic should soft replace", "app dir - basic known bugs should support React fetch instrumentation client component", "app dir - basic known bugs should support React fetch instrumentation client component client-navigation", "app dir - basic should handle css imports in next/dynamic correctly", diff --git a/test/turbopack-dev-tests-manifest.json b/test/turbopack-dev-tests-manifest.json index 31a0b6aee218d..781694b98516c 100644 --- a/test/turbopack-dev-tests-manifest.json +++ b/test/turbopack-dev-tests-manifest.json @@ -2674,9 +2674,9 @@ "flakey": [], "runtimeError": false }, - "test/e2e/app-dir/app-client-cache/client-cache.test.ts": { + "test/e2e/app-dir/app-client-cache/client-cache.original.test.ts": { "passed": [ - "app dir client cache semantics should renew the 30s cache once the data is revalidated" + "app dir client cache semantics (30s/5min) should renew the 30s cache once the data is revalidated" ], "failed": [], "pending": [],