Skip to content

Commit

Permalink
Ignore RSC fetch errors after hard navigation (#73975)
Browse files Browse the repository at this point in the history
When the user reloads the page or navigates away while RSC fetch
requests are still in flight – most notably background prefetches – we
currently log the following error:

```
Failed to fetch RSC payload for <URL>. Falling back to browser navigation.
```

This unnecessarily clutters the DevTools console and creates noise in
remote error reporting tools.

When the browser cancels a `fetch` request due to navigation, it throws
`TypeError: Failed to fetch`. We shouldn't universally suppress this
error since it may also occur for other valid reasons where we do want
to log it.

To suppress the error only in genuine navigation scenarios, we introduce
an `AbortController` and pass its signal to the `fetch` call. On
`'pagehide'` events, we abort the controller. This approach ensures that
error log suppression is limited solely to these navigation-related
cases.

[x-ref](https://vercel.slack.com/archives/C0676QZBWKS/p1734009715952909)
fixes #60549
  • Loading branch information
unstubbable authored Dec 16, 2024
1 parent a1f2633 commit ac89400
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ function doMpaNavigation(url: string): FetchServerResponseResult {
}
}

// TODO: Figure out why this module is included in server page bundles.
const win = typeof window === 'undefined' ? undefined : window
const abortController = new AbortController()

// Abort any in-flight requests when the page is unloaded, e.g. due to reloading
// the page or performing hard navigations. This allows us to ignore what would
// otherwise be a thrown TypeError when the browser cancels the requests.
win?.addEventListener('pagehide', () => {
abortController.abort()
})

/**
* Fetch the flight data for the provided url. Takes in the current router state
* to decide what to render server-side.
Expand Down Expand Up @@ -202,10 +213,13 @@ export async function fetchServerResponse(
staleTime,
}
} catch (err) {
console.error(
`Failed to fetch RSC payload for ${url}. Falling back to browser navigation.`,
err
)
if (!abortController.signal.aborted) {
console.error(
`Failed to fetch RSC payload for ${url}. Falling back to browser navigation.`,
err
)
}

// If fetch fails handle it like a mpa navigation
// TODO-APP: Add a test for the case where a CORS request fails, e.g. external url redirect coming from the response.
// See https://github.com/vercel/next.js/issues/43605#issuecomment-1451617521 for a reproduction.
Expand All @@ -223,7 +237,8 @@ export async function fetchServerResponse(
export function createFetch(
url: URL,
headers: RequestHeaders,
fetchPriority: 'auto' | 'high' | 'low' | null
fetchPriority: 'auto' | 'high' | 'low' | null,
signal?: AbortSignal
) {
const fetchUrl = new URL(url)

Expand Down Expand Up @@ -266,6 +281,7 @@ export function createFetch(
credentials: 'same-origin',
headers,
priority: fetchPriority || undefined,
signal,
})
}

Expand Down
15 changes: 15 additions & 0 deletions test/e2e/app-dir/app-prefetch/prefetching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ describe('app dir - prefetching', () => {
expect(next.cliOutput).not.toContain('is not defined')
})

it('should not have prefetch error when reloading before prefetch request is finished', async () => {
const browser = await next.browser('/')
await browser.eval('window.nd.router.prefetch("/dashboard/123")')
await browser.refresh()
const logs = await browser.log()

expect(logs).not.toMatchObject(
expect.arrayContaining([
expect.objectContaining({
message: expect.stringContaining('Failed to fetch RSC payload'),
}),
])
)
})

it('should not fetch again when a static page was prefetched', async () => {
const browser = await next.browser('/404', browserConfigWithFixedTime)
let requests: string[] = []
Expand Down

0 comments on commit ac89400

Please sign in to comment.