Link Component does not prefetch on network recovery.
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/refirst11/reproduction-app
To Reproduce
Sometimes unnecessary mounts are run.
And canary release e2e testing is special.
Let's say the start of the route is "/" and it goes offline in this state.
"/a", "/b", and "/c" are in the viewport at this point, so there will be a smooth transition when transitioning to them.
And it is not possible to transition to links "/d", "/e", and "/f" on pages a, b, and c at this point, but let's say the network is restored, for example in a subway or train tunnel. In that case, when transitioning to pages d, e, and f, a network request will be sent and the page will be reloaded as in the a tag.
I call this mounting on recovery in Next.js.
However, since prefetch only needs to hit when the network restarts,
I thought it would be fixed by adding the following to the dependencies in React.useEffect.
In my environment, when I tried e2e testing next.js,
pnpm test-dev test/e2e/app-dir/app-prefetch/prefetching.test.ts
It's possible that I overlooked this, but all of the test code in the describe block of prefetching.test.ts passes (even if it fails), so I decided to write this as an issue here.
Current vs. Expected behavior
When the network becomes online, link components in the viewport are prefetched.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.0.0: Sat Jul 13 00:56:26 PDT 2024; root:xnu-11215.0.165.0.4~50/RELEASE_ARM64_T8103
Available memory (MB): 16384
Available CPU cores: 8
Binaries:
Node: 22.8.0
npm: 10.8.2
Yarn: N/A
pnpm: 9.6.0
Relevant Packages:
next: 14.2.5 // There is a newer version (14.2.9) available, upgrade recommended!
eslint-config-next: 13.5.6
react: 18.3.1
react-dom: 18.3.1
typescript: 5.5.4
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
create-next-app, Performance
Which stage(s) are affected? (Select all that apply)
next dev (local), next start (local), Vercel (Deployed), Other (Deployed)
Additional context
Deploy is with vercel and next start.
I've been using Next.js since version 11, but even back then there was no prefetching during network restarts.
I want to improve
// use-network.ts
import { useSyncExternalStore } from 'react'
function subscribe(callback: () => void) {
window.addEventListener('online', callback)
window.addEventListener('offline', callback)
return () => {
window.removeEventListener('online', callback)
window.removeEventListener('offline', callback)
}
}
export function useNetwork() {
return useSyncExternalStore(
subscribe,
() => navigator.onLine,
() => true
)
}
// link.tsx
...
const [setIntersectionRef, isVisible, resetVisible] = useIntersection({
rootMargin: '200px',
})
const isOnline = useNetwork()
React.useEffect(() => {
// in dev, we only prefetch on hover to avoid wasting resources as the prefetch will trigger compiling the page.
if (process.env.NODE_ENV !== 'production') {
return
}
if (!router) {
return
}
// If we don't need to prefetch the URL, don't do prefetch.
if (!isVisible || !isOnline || !prefetchEnabled) {
return
}
// Prefetch the URL.
prefetch(
router,
href,
as,
{ locale },
{
kind: appPrefetchKind,
},
isAppRouter
)
}, [
as,
href,
isVisible,
isOnline,
locale,
prefetchEnabled,
pagesRouter?.locale,
router,
isAppRouter,
appPrefetchKind,
])
e2e test case
describe('online/offline transitions', () => {
if (isNextDeploy) return
it('should prefetch links in viewport after network recovery', async () => {
const requests: string[] = []
let isOffline = false
const browser = await next.browser('/', {
async beforePageLoad(page) {
// Intercept RSC prefetch requests and abort them while offline
await page.route('**/*', (route) => {
const url = new URL(route.request().url())
if (isOffline && url.searchParams.has('_rsc')) {
route.abort('internetdisconnected')
} else {
route.continue()
}
})
// Track all RSC requests including aborted ones
page.on('request', async (req) => {
const url = new URL(req.url())
if (url.searchParams.has('_rsc')) {
requests.push(url.pathname)
}
})
},
})
// Simulate going offline: spoof navigator.onLine and dispatch the offline event
isOffline = true
await browser.eval(`
Object.defineProperty(navigator, 'onLine', { get: () => false, configurable: true })
window.dispatchEvent(new Event('offline'))
`)
await waitFor(300)
// Expand the accordion to make the /dashboard link visible while offline
await browser.elementByCss('#accordion-to-dashboard').click()
await browser.waitForElementByCss('#to-dashboard')
const requestsCountBeforeRecovery = requests.filter((req) =>
req.includes('/dashboard')
).length
// Simulate network recovery: restore navigator.onLine and dispatch the online event.
// This should cause useNetwork() to return true, triggering the prefetch useEffect to re-run.
isOffline = false
await browser.eval(`
Object.defineProperty(navigator, 'onLine', { get: () => true, configurable: true })
window.dispatchEvent(new Event('online'))
`)
await browser.waitForIdleNetwork()
// Verify that prefetch was retried after the online event
await retry(async () => {
const dashboardRequests = requests.filter((req) =>
req.includes('/dashboard')
)
expect(dashboardRequests.length).toBeGreaterThan(
requestsCountBeforeRecovery
)
})
})
})
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running pnpm test-dev test/e2e/app-dir/app-prefetch/prefetching.test.ts and read its online/offline transitions case. Then trace the Link prefetch logic in link.tsx and the reproduction app linked in the issue. Done means the viewport link issues a new prefetch request after the online event, with the e2e test passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nextjs, react, typescript
- Domain
- frontend, performance, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100