Skip to content

Commit 64b69ff

Browse files
authored
Avoid duplicate WebSocket connection for global error pages (#82788)
When we're rendering a global error page (something with `<html id="__next_error__">`), we are wrapping the root element in a component that renders a top-level dev overlay to show the global error. The function that's wrapping this element was previously creating an unnecessary second WebSocket connection to handle the case where the error is removed by editing a server component, and the page is subsequently reloaded. However, this can be handled by the first WebSocket connection that's created in the root element just fine, we just need to ensure that the reload is also triggered when `document.documentElement.id` is `'__next_error__'`, which is the same [condition we use for rendering the wrapper](https://github.com/vercel/next.js/blob/a0b7a725a1b424473940fe3111842ae92d918a25/packages/next/src/client/app-index.tsx#L275). This change is covered by existing tests, e.g. `test/development/app-dir/missing-required-html-tags/index.test.ts`.
1 parent b6b6294 commit 64b69ff

File tree

4 files changed

+17
-31
lines changed

4 files changed

+17
-31
lines changed

packages/next/src/client/app-index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,13 @@ export function hydrate(
276276
let element = reactEl
277277
// Server rendering failed, fall back to client-side rendering
278278
if (process.env.NODE_ENV !== 'production') {
279-
const { createRootLevelDevOverlayElement } =
279+
const { RootLevelDevOverlayElement } =
280280
require('../next-devtools/userspace/app/client-entry') as typeof import('../next-devtools/userspace/app/client-entry')
281281

282282
// Note this won't cause hydration mismatch because we are doing CSR w/o hydration
283-
element = createRootLevelDevOverlayElement(element)
283+
element = (
284+
<RootLevelDevOverlayElement>{element}</RootLevelDevOverlayElement>
285+
)
284286
}
285287

286288
ReactDOMClient.createRoot(appElement, reactRootOptions).render(element)

packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,10 @@ function processMessage(
391391
// server with any subsequent requests.
392392
document.cookie = `${NEXT_HMR_REFRESH_HASH_COOKIE}=${obj.hash};path=/`
393393

394-
if (RuntimeErrorHandler.hadRuntimeError) {
394+
if (
395+
RuntimeErrorHandler.hadRuntimeError ||
396+
document.documentElement.id === '__next_error__'
397+
) {
395398
if (reloading) return
396399
reloading = true
397400
return window.location.reload()
Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,17 @@
11
import React from 'react'
2-
import { getSocketUrl } from '../../../client/dev/hot-reloader/get-socket-url'
3-
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../../server/dev/hot-reloader-types'
42
import DefaultGlobalError from '../../../client/components/builtin/global-error'
53
import { AppDevOverlayErrorBoundary } from './app-dev-overlay-error-boundary'
64

7-
// if an error is thrown while rendering an RSC stream, this will catch it in dev
8-
// and show the error overlay
9-
export function createRootLevelDevOverlayElement(reactEl: React.ReactElement) {
10-
const socketUrl = getSocketUrl(process.env.__NEXT_ASSET_PREFIX || '')
11-
const socket = new window.WebSocket(`${socketUrl}/_next/webpack-hmr`)
12-
13-
// add minimal "hot reload" support for RSC errors
14-
const handler = (event: MessageEvent) => {
15-
let obj
16-
try {
17-
obj = JSON.parse(event.data)
18-
} catch {}
19-
20-
if (!obj || !('action' in obj)) {
21-
return
22-
}
23-
24-
if (obj.action === HMR_ACTIONS_SENT_TO_BROWSER.SERVER_COMPONENT_CHANGES) {
25-
window.location.reload()
26-
}
27-
}
28-
29-
socket.addEventListener('message', handler)
30-
5+
// If an error is thrown while rendering an RSC stream, this will catch it in
6+
// dev and show the error overlay.
7+
export function RootLevelDevOverlayElement({
8+
children,
9+
}: {
10+
children: React.ReactNode
11+
}) {
3112
return (
3213
<AppDevOverlayErrorBoundary globalError={[DefaultGlobalError, null]}>
33-
{reactEl}
14+
{children}
3415
</AppDevOverlayErrorBoundary>
3516
)
3617
}

packages/next/src/server/stream-utils/node-web-streams-helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ export function createRootLayoutValidatorStream(): TransformStream<
636636
.map((c) => `<${c}>`)
637637
.join(
638638
missingTags.length > 1 ? ' and ' : ''
639-
)} tags in the root layout.\nRead more at https://nextjs.org/docs/messages/missing-root-layout-tags""
639+
)} tags in the root layout.\nRead more at https://nextjs.org/docs/messages/missing-root-layout-tags"
640640
data-next-error-digest="${MISSING_ROOT_TAGS_ERROR}"
641641
data-next-error-stack=""
642642
></template>

0 commit comments

Comments
 (0)