From 2b6b7254b70848007250afe20f0833adadcaaa89 Mon Sep 17 00:00:00 2001
From: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Date: Wed, 7 Aug 2024 17:50:28 -0700
Subject: [PATCH] fix app router prefetch deduping
---
packages/next/src/client/link.tsx | 7 ++--
.../app-prefetch/app/prefetch-race/page.js | 19 +++++++++++
.../app-dir/app-prefetch/prefetching.test.ts | 33 ++++++++++++++++++-
3 files changed, 56 insertions(+), 3 deletions(-)
create mode 100644 test/e2e/app-dir/app-prefetch/app/prefetch-race/page.js
diff --git a/packages/next/src/client/link.tsx b/packages/next/src/client/link.tsx
index d10cc2f6670b0..8063ddd07a14c 100644
--- a/packages/next/src/client/link.tsx
+++ b/packages/next/src/client/link.tsx
@@ -148,8 +148,11 @@ function prefetch(
}
// We should only dedupe requests when experimental.optimisticClientCache is
- // disabled.
- if (!options.bypassPrefetchedCheck) {
+ // disabled & when we're not using the app router. App router handles
+ // reusing an existing prefetch entry (if it exists) for the same URL.
+ // If we dedupe in here, we will cause a race where different prefetch kinds
+ // to the same URL (ie auto vs true) will cause one to be ignored.
+ if (!options.bypassPrefetchedCheck && !isAppRouter) {
const locale =
// Let the link's locale prop override the default router locale.
typeof options.locale !== 'undefined'
diff --git a/test/e2e/app-dir/app-prefetch/app/prefetch-race/page.js b/test/e2e/app-dir/app-prefetch/app/prefetch-race/page.js
new file mode 100644
index 0000000000000..e01e7a2d0f9ea
--- /dev/null
+++ b/test/e2e/app-dir/app-prefetch/app/prefetch-race/page.js
@@ -0,0 +1,19 @@
+import Link from 'next/link'
+import React from 'react'
+
+export default function Page() {
+ return (
+ <>
+
+
+ /force-dynamic/test-page (prefetch: auto)
+
+
+
+
+ /force-dynamic/test-page (prefetch: true)
+
+
+ >
+ )
+}
diff --git a/test/e2e/app-dir/app-prefetch/prefetching.test.ts b/test/e2e/app-dir/app-prefetch/prefetching.test.ts
index df00f5ea86355..f3ec1101cbf26 100644
--- a/test/e2e/app-dir/app-prefetch/prefetching.test.ts
+++ b/test/e2e/app-dir/app-prefetch/prefetching.test.ts
@@ -1,6 +1,6 @@
import { nextTestSetup } from 'e2e-utils'
import { check, waitFor, retry } from 'next-test-utils'
-import type { Page, Request } from 'playwright'
+import type { Page, Request, Route } from 'playwright'
import { NEXT_RSC_UNION_QUERY } from 'next/dist/client/components/app-router-headers'
const browserConfigWithFixedTime = {
@@ -462,5 +462,36 @@ describe('app dir - prefetching', () => {
expect(dashboardRequests[1].priority).toBe('auto') // the second request is the lazy fetch to fill in missing data
})
})
+
+ it('should respect multiple prefetch types to the same URL', async () => {
+ let interceptRequests = false
+
+ const browser = await next.browser('/prefetch-race', {
+ beforePageLoad(page: Page) {
+ page.route('**/force-dynamic/**', async (route: Route) => {
+ if (!interceptRequests) {
+ return route.continue()
+ }
+
+ const request = route.request()
+ const headers = await request.allHeaders()
+
+ if (headers['rsc'] === '1') {
+ // intentionally stall the request,
+ // as after the initial page load, there shouldn't be any additional fetches
+ // since the data should already be available.
+ } else {
+ await route.continue()
+ }
+ })
+ },
+ })
+
+ await browser.waitForIdleNetwork()
+ interceptRequests = true
+
+ await browser.elementByCss('[href="/force-dynamic/test-page"]').click()
+ await browser.waitForElementByCss('#test-page')
+ })
})
})