Skip to content

Commit

Permalink
Trigger error for stream on client when it is not finished as expected (
Browse files Browse the repository at this point in the history
#65798)

### What

* Error on case that readable stream controller tries to close but
stream is either errored or unfinished
* Delay the DOMContentLoaded event to avoid "Connection Closed" error


### Why

Regarding to the new error about readbale stream controller, the new
error we added here is for identifying the connection is closing before
the last bit has been received that's an error case not intentional
close. It's not a proper state for close more for erroring.


As for adding the queuing for the task of dom content loaded callback,
after investigation, I found that if the DOMLoaded execute earlier the
readable stream controller cuold close earlier on client, which lead to
the "Connection Closed." error. Hence we added a `queueMicroTask` here
to delay it after hydrate call.

x-ref: [slack
thread](https://vercel.slack.com/archives/C01A2M9R8RZ/p1715188343813489)
  • Loading branch information
huozhi authored May 28, 2024
1 parent d739df1 commit 25db485
Showing 1 changed file with 17 additions and 2 deletions.
19 changes: 17 additions & 2 deletions packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ function nextServerDataCallback(
}
}

function isStreamErrorOrUnfinished(ctr: ReadableStreamDefaultController) {
// If `desiredSize` is null, it means the stream is closed or errored. If it is lower than 0, the stream is still unfinished.
return ctr.desiredSize === null || ctr.desiredSize < 0
}

// There might be race conditions between `nextServerDataRegisterWriter` and
// `DOMContentLoaded`. The former will be called when React starts to hydrate
// the root, the latter will be called when the DOM is fully loaded.
Expand All @@ -87,7 +92,15 @@ function nextServerDataRegisterWriter(ctr: ReadableStreamDefaultController) {
ctr.enqueue(encoder.encode(val))
})
if (initialServerDataLoaded && !initialServerDataFlushed) {
ctr.close()
if (isStreamErrorOrUnfinished(ctr)) {
ctr.error(
new Error(
'The connection to the page was unexpectedly closed, possibly due to the stop button being clicked, loss of Wi-Fi, or an unstable internet connection.'
)
)
} else {
ctr.close()
}
initialServerDataFlushed = true
initialServerDataBuffer = undefined
}
Expand All @@ -105,11 +118,13 @@ const DOMContentLoaded = function () {
}
initialServerDataLoaded = true
}

// It's possible that the DOM is already loaded.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', DOMContentLoaded, false)
} else {
DOMContentLoaded()
// Delayed in marco task to ensure it's executed later than hydration
setTimeout(DOMContentLoaded)
}

const nextServerDataLoadingGlobal = ((self as any).__next_f =
Expand Down

0 comments on commit 25db485

Please sign in to comment.