Skip to content

Commit 459e401

Browse files
committed
[dev-overlay] Unify error interception between app/ and pages/
1 parent bc2964c commit 459e401

File tree

5 files changed

+54
-112
lines changed

5 files changed

+54
-112
lines changed

Diff for: packages/next/src/client/components/errors/use-error-handler.ts

+44-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from 'react'
1+
import { useCallback, useEffect } from 'react'
22
import { attachHydrationErrorState } from './attach-hydration-error-state'
33
import { isNextRouterError } from '../is-next-router-error'
44
import { storeHydrationErrorStateFromConsoleArgs } from './hydration-error-info'
@@ -7,6 +7,13 @@ import isError from '../../../lib/is-error'
77
import { createConsoleError } from './console-error'
88
import { enqueueConsecutiveDedupedError } from './enqueue-client-error'
99
import { getReactStitchedError } from '../errors/stitched-error'
10+
import {
11+
ACTION_UNHANDLED_ERROR,
12+
ACTION_UNHANDLED_REJECTION,
13+
type useErrorOverlayReducer,
14+
} from '../react-dev-overlay/shared'
15+
import { parseStack } from '../react-dev-overlay/utils/parse-stack'
16+
import { parseComponentStack } from '../react-dev-overlay/utils/parse-component-stack'
1017

1118
const queueMicroTask =
1219
globalThis.queueMicrotask || ((cb: () => void) => Promise.resolve().then(cb))
@@ -70,7 +77,42 @@ export function handleClientError(originError: unknown) {
7077
}
7178
}
7279

73-
export function useErrorHandler(
80+
export function useErrorHandlers(
81+
dispatch: ReturnType<typeof useErrorOverlayReducer>[1]
82+
): void {
83+
const handleOnUnhandledError = useCallback(
84+
(error: Error): void => {
85+
// Component stack is added to the error in use-error-handler in case there was a hydration error
86+
const componentStackTrace = (error as any)._componentStack
87+
88+
dispatch({
89+
type: ACTION_UNHANDLED_ERROR,
90+
reason: error,
91+
frames: parseStack(error.stack || ''),
92+
componentStackFrames:
93+
typeof componentStackTrace === 'string'
94+
? parseComponentStack(componentStackTrace)
95+
: undefined,
96+
})
97+
},
98+
[dispatch]
99+
)
100+
101+
const handleOnUnhandledRejection = useCallback(
102+
(reason: Error): void => {
103+
const stitchedError = getReactStitchedError(reason)
104+
dispatch({
105+
type: ACTION_UNHANDLED_REJECTION,
106+
reason: stitchedError,
107+
frames: parseStack(stitchedError.stack || ''),
108+
})
109+
},
110+
[dispatch]
111+
)
112+
useErrorHandler(handleOnUnhandledError, handleOnUnhandledRejection)
113+
}
114+
115+
function useErrorHandler(
74116
handleOnUnhandledError: ErrorHandler,
75117
handleOnUnhandledRejection: ErrorHandler
76118
) {

Diff for: packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx

+3-38
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference types="webpack/module.d.ts" />
22

33
import type { ReactNode } from 'react'
4-
import { useCallback, useEffect, startTransition, useMemo, useRef } from 'react'
4+
import { useEffect, startTransition, useMemo, useRef } from 'react'
55
import stripAnsi from 'next/dist/compiled/strip-ansi'
66
import formatWebpackMessages from '../utils/format-webpack-messages'
77
import { useRouter } from '../../navigation'
@@ -13,24 +13,20 @@ import {
1313
ACTION_DEV_INDICATOR,
1414
ACTION_REFRESH,
1515
ACTION_STATIC_INDICATOR,
16-
ACTION_UNHANDLED_ERROR,
17-
ACTION_UNHANDLED_REJECTION,
1816
ACTION_VERSION_INFO,
1917
REACT_REFRESH_FULL_RELOAD,
2018
reportInvalidHmrMessage,
2119
useErrorOverlayReducer,
2220
} from '../shared'
23-
import { parseStack } from '../utils/parse-stack'
2421
import { AppDevOverlay } from './app-dev-overlay'
25-
import { useErrorHandler } from '../../errors/use-error-handler'
22+
import { useErrorHandlers } from '../../errors/use-error-handler'
2623
import { RuntimeErrorHandler } from '../../errors/runtime-error-handler'
2724
import {
2825
useSendMessage,
2926
useTurbopack,
3027
useWebsocket,
3128
useWebsocketPing,
3229
} from '../utils/use-websocket'
33-
import { parseComponentStack } from '../utils/parse-component-stack'
3430
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
3531
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../../../server/dev/hot-reloader-types'
3632
import type {
@@ -40,7 +36,6 @@ import type {
4036
import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from '../shared'
4137
import type { DebugInfo } from '../types'
4238
import { useUntrackedPathname } from '../../navigation-untracked'
43-
import { getReactStitchedError } from '../../errors/stitched-error'
4439
import { handleDevBuildIndicatorHmrEvents } from '../../../dev/dev-build-indicator/internal/handle-dev-build-indicator-hmr-events'
4540
import type { GlobalErrorComponent } from '../../error-boundary'
4641
import type { DevIndicatorServerState } from '../../../../server/dev/dev-indicator-server-state'
@@ -480,6 +475,7 @@ export default function HotReload({
480475
globalError: [GlobalErrorComponent, React.ReactNode]
481476
}) {
482477
const [state, dispatch] = useErrorOverlayReducer('app')
478+
useErrorHandlers(dispatch)
483479

484480
const dispatcher = useMemo<Dispatcher>(() => {
485481
return {
@@ -513,37 +509,6 @@ export default function HotReload({
513509
}
514510
}, [dispatch])
515511

516-
const handleOnUnhandledError = useCallback(
517-
(error: Error): void => {
518-
// Component stack is added to the error in use-error-handler in case there was a hydration error
519-
const componentStackTrace = (error as any)._componentStack
520-
521-
dispatch({
522-
type: ACTION_UNHANDLED_ERROR,
523-
reason: error,
524-
frames: parseStack(error.stack || ''),
525-
componentStackFrames:
526-
typeof componentStackTrace === 'string'
527-
? parseComponentStack(componentStackTrace)
528-
: undefined,
529-
})
530-
},
531-
[dispatch]
532-
)
533-
534-
const handleOnUnhandledRejection = useCallback(
535-
(reason: Error): void => {
536-
const stitchedError = getReactStitchedError(reason)
537-
dispatch({
538-
type: ACTION_UNHANDLED_REJECTION,
539-
reason: stitchedError,
540-
frames: parseStack(stitchedError.stack || ''),
541-
})
542-
},
543-
[dispatch]
544-
)
545-
useErrorHandler(handleOnUnhandledError, handleOnUnhandledRejection)
546-
547512
const webSocketRef = useWebsocket(assetPrefix)
548513
useWebsocketPing(webSocketRef)
549514
const sendMessage = useSendMessage(webSocketRef)

Diff for: packages/next/src/client/components/react-dev-overlay/pages/client.ts

+4-71
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,20 @@
11
import * as Bus from './bus'
2-
import { parseStack } from '../utils/parse-stack'
3-
import { parseComponentStack } from '../utils/parse-component-stack'
4-
import { storeHydrationErrorStateFromConsoleArgs } from '../../errors/hydration-error-info'
52
import {
63
ACTION_BEFORE_REFRESH,
74
ACTION_BUILD_ERROR,
85
ACTION_BUILD_OK,
96
ACTION_DEV_INDICATOR,
107
ACTION_REFRESH,
118
ACTION_STATIC_INDICATOR,
12-
ACTION_UNHANDLED_ERROR,
13-
ACTION_UNHANDLED_REJECTION,
149
ACTION_VERSION_INFO,
1510
} from '../shared'
1611
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
17-
import { attachHydrationErrorState } from '../../errors/attach-hydration-error-state'
1812
import type { DevIndicatorServerState } from '../../../../server/dev/dev-indicator-server-state'
13+
import { handleGlobalErrors } from '../../errors/use-error-handler'
14+
import { patchConsoleError } from '../../globals/intercept-console-error'
1915

2016
let isRegistered = false
2117

22-
function handleError(error: unknown) {
23-
if (!error || !(error instanceof Error) || typeof error.stack !== 'string') {
24-
// A non-error was thrown, we don't have anything to show. :-(
25-
return
26-
}
27-
28-
attachHydrationErrorState(error)
29-
30-
const componentStackTrace = (error as any)._componentStack
31-
const componentStackFrames =
32-
typeof componentStackTrace === 'string'
33-
? parseComponentStack(componentStackTrace)
34-
: undefined
35-
36-
// Skip ModuleBuildError and ModuleNotFoundError, as it will be sent through onBuildError callback.
37-
// This is to avoid same error as different type showing up on client to cause flashing.
38-
if (
39-
error.name !== 'ModuleBuildError' &&
40-
error.name !== 'ModuleNotFoundError'
41-
) {
42-
Bus.emit({
43-
type: ACTION_UNHANDLED_ERROR,
44-
reason: error,
45-
frames: parseStack(error.stack),
46-
componentStackFrames,
47-
})
48-
}
49-
}
50-
51-
let origConsoleError = console.error
52-
function nextJsHandleConsoleError(...args: any[]) {
53-
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
54-
const error = process.env.NODE_ENV !== 'production' ? args[1] : args[0]
55-
storeHydrationErrorStateFromConsoleArgs(...args)
56-
handleError(error)
57-
origConsoleError.apply(window.console, args)
58-
}
59-
60-
function onUnhandledError(event: ErrorEvent) {
61-
const error = event?.error
62-
handleError(error)
63-
}
64-
65-
function onUnhandledRejection(ev: PromiseRejectionEvent) {
66-
const reason = ev?.reason
67-
if (
68-
!reason ||
69-
!(reason instanceof Error) ||
70-
typeof reason.stack !== 'string'
71-
) {
72-
// A non-error was thrown, we don't have anything to show. :-(
73-
return
74-
}
75-
76-
const e = reason
77-
Bus.emit({
78-
type: ACTION_UNHANDLED_REJECTION,
79-
reason: reason,
80-
frames: parseStack(e.stack!),
81-
})
82-
}
83-
8418
export function register() {
8519
if (isRegistered) {
8620
return
@@ -91,9 +25,8 @@ export function register() {
9125
Error.stackTraceLimit = 50
9226
} catch {}
9327

94-
window.addEventListener('error', onUnhandledError)
95-
window.addEventListener('unhandledrejection', onUnhandledRejection)
96-
window.console.error = nextJsHandleConsoleError
28+
handleGlobalErrors()
29+
patchConsoleError()
9730
}
9831

9932
export function onBuildOk() {

Diff for: packages/next/src/client/components/react-dev-overlay/pages/hooks.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React from 'react'
22
import * as Bus from './bus'
33
import { useErrorOverlayReducer } from '../shared'
4+
import { useErrorHandlers } from '../../errors/use-error-handler'
45
import { Router } from '../../../router'
56

67
export const usePagesDevOverlay = () => {
78
const [state, dispatch] = useErrorOverlayReducer('pages')
9+
useErrorHandlers(dispatch)
810

911
React.useEffect(() => {
1012
Bus.on(dispatch)

Diff for: packages/next/src/client/components/react-dev-overlay/shared.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function pushErrorFilterDuplicates(
116116
const shouldDisableDevIndicator =
117117
process.env.__NEXT_DEV_INDICATOR?.toString() === 'false'
118118

119-
export const INITIAL_OVERLAY_STATE: Omit<OverlayState, 'routerType'> = {
119+
const INITIAL_OVERLAY_STATE: Omit<OverlayState, 'routerType'> = {
120120
nextId: 1,
121121
buildError: null,
122122
errors: [],

0 commit comments

Comments
 (0)