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": [],