diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 9f846681accb0d1..c13fd1bc2bfac81 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -57,7 +57,7 @@ let observer: IntersectionObserver const listeners = new Map() const IntersectionObserver = typeof window !== 'undefined' ? (window as any).IntersectionObserver : null -const prefetched: { [href: string]: boolean } = {} +const prefetched: { [cacheKey: string]: boolean } = {} function getObserver() { // Return shared instance of IntersectionObserver if already created @@ -139,10 +139,16 @@ class Link extends Component { } handleRef(ref: Element) { - const isPrefetched = prefetched[this.getPaths()[0]] if (this.p && IntersectionObserver && ref && ref.tagName) { this.cleanUpListeners() + const isPrefetched = + prefetched[ + this.getPaths().join( + // Join on an invalid URI character + '%' + ) + ] if (!isPrefetched) { this.cleanUpListeners = listenToIntersections(ref, () => { this.prefetch() @@ -208,17 +214,24 @@ class Link extends Component { prefetch(options?: PrefetchOptions) { if (!this.p || typeof window === 'undefined') return // Prefetch the JSON page if asked (only in the client) - const [href, asPath] = this.getPaths() + const paths = this.getPaths() // We need to handle a prefetch error here since we may be // loading with priority which can reject but we don't // want to force navigation since this is only a prefetch - Router.prefetch(href, asPath, options).catch(err => { - if (process.env.NODE_ENV !== 'production') { - // rethrow to show invalid URL errors - throw err + Router.prefetch(paths[/* href */ 0], paths[/* asPath */ 1], options).catch( + err => { + if (process.env.NODE_ENV !== 'production') { + // rethrow to show invalid URL errors + throw err + } } - }) - prefetched[href] = true + ) + prefetched[ + paths.join( + // Join on an invalid URI character + '%' + ) + ] = true } render() { diff --git a/test/integration/preload-viewport/pages/not-de-duped.js b/test/integration/preload-viewport/pages/not-de-duped.js new file mode 100644 index 000000000000000..c8b164baeb8fb5e --- /dev/null +++ b/test/integration/preload-viewport/pages/not-de-duped.js @@ -0,0 +1,11 @@ +import Link from 'next/link' + +export default () => { + return ( +

+ + to /first + +

+ ) +} diff --git a/test/integration/preload-viewport/test/index.test.js b/test/integration/preload-viewport/test/index.test.js index 0df30ea2464f50e..25f40cb72595b11 100644 --- a/test/integration/preload-viewport/test/index.test.js +++ b/test/integration/preload-viewport/test/index.test.js @@ -228,4 +228,20 @@ describe('Prefetching Links in viewport', () => { const calledPrefetch = await browser.eval(`window.calledPrefetch`) expect(calledPrefetch).toBe(false) }) + + it('should prefetch with a different asPath for a prefetched page', async () => { + // info: both `/` and `/not-de-duped` ref the `/first` page, which we want + // to see prefetched twice. + const browser = await webdriver(appPort, '/') + await browser.eval(`(function() { + window.calledPrefetch = false + window.next.router.prefetch = function() { + window.calledPrefetch = true + } + window.next.router.push('/not-de-duped') + })()`) + await waitFor(2 * 1000) + const calledPrefetch = await browser.eval(`window.calledPrefetch`) + expect(calledPrefetch).toBe(true) + }) })