1- import { PureComponent } from 'react'
1+ import { PureComponent , useEffect } from 'react'
22import { RuntimeErrorHandler } from '../../errors/runtime-error-handler'
3+ import { handleClientError } from '../../errors/use-error-handler'
34import { ErrorBoundary } from '../../error-boundary'
45import DefaultGlobalError , {
56 type GlobalErrorComponent ,
67} from '../../global-error'
8+ import { isNextRouterError } from '../../is-next-router-error'
9+ import { ACTION_ERROR_OVERLAY_OPEN , type OverlayDispatch } from '../shared'
10+ import { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants'
711
812type AppDevOverlayErrorBoundaryProps = {
913 children : React . ReactNode
1014 globalError : [ GlobalErrorComponent , React . ReactNode ]
11- onError : ( ) => void
15+ dispatch : OverlayDispatch
1216}
1317
1418type AppDevOverlayErrorBoundaryState = {
@@ -38,6 +42,65 @@ function ErroredHtml({
3842 )
3943}
4044
45+ function readSsrError ( ) : ( Error & { digest ?: string } ) | null {
46+ if ( typeof document === 'undefined' ) {
47+ return null
48+ }
49+
50+ const ssrErrorTemplateTag = document . querySelector (
51+ 'template[data-next-error-message]'
52+ )
53+ if ( ssrErrorTemplateTag ) {
54+ const message : string = ssrErrorTemplateTag . getAttribute (
55+ 'data-next-error-message'
56+ ) !
57+ const stack = ssrErrorTemplateTag . getAttribute ( 'data-next-error-stack' )
58+ const digest = ssrErrorTemplateTag . getAttribute ( 'data-next-error-digest' )
59+ const error = new Error ( message )
60+ if ( digest ) {
61+ ; ( error as any ) . digest = digest
62+ }
63+ // Skip Next.js SSR'd internal errors that which will be handled by the error boundaries.
64+ if ( isNextRouterError ( error ) ) {
65+ return null
66+ }
67+ error . stack = stack || ''
68+ return error
69+ }
70+
71+ return null
72+ }
73+
74+ // Needs to be in the same error boundary as the shell.
75+ // If it commits, we know we recovered from an SSR error.
76+ // If it doesn't commit, we errored again and React will take care of error reporting.
77+ function ReplaySsrOnlyErrors ( {
78+ onBlockingError,
79+ } : {
80+ onBlockingError : ( ) => void
81+ } ) {
82+ if ( process . env . NODE_ENV !== 'production' ) {
83+ // Need to read during render. The attributes will be gone after commit.
84+ const ssrError = readSsrError ( )
85+ // eslint-disable-next-line react-hooks/rules-of-hooks
86+ useEffect ( ( ) => {
87+ if ( ssrError !== null ) {
88+ // TODO(veil): Include original Owner Stack (NDX-905)
89+ // TODO(veil): Mark as recoverable error
90+ // TODO(veil): console.error
91+ handleClientError ( ssrError )
92+
93+ // If it's missing root tags, we can't recover, make it blocking.
94+ if ( ssrError . digest === MISSING_ROOT_TAGS_ERROR ) {
95+ onBlockingError ( )
96+ }
97+ }
98+ } , [ ssrError , onBlockingError ] )
99+ }
100+
101+ return null
102+ }
103+
41104export class AppDevOverlayErrorBoundary extends PureComponent <
42105 AppDevOverlayErrorBoundaryProps ,
43106 AppDevOverlayErrorBoundaryState
@@ -52,8 +115,14 @@ export class AppDevOverlayErrorBoundary extends PureComponent<
52115 }
53116 }
54117
118+ openErrorOverlay = ( ) => {
119+ this . props . dispatch ( {
120+ type : ACTION_ERROR_OVERLAY_OPEN ,
121+ } )
122+ }
123+
55124 componentDidCatch ( ) {
56- this . props . onError ( )
125+ this . openErrorOverlay ( )
57126 }
58127
59128 render ( ) {
@@ -64,6 +133,13 @@ export class AppDevOverlayErrorBoundary extends PureComponent<
64133 < ErroredHtml globalError = { globalError } error = { reactError } />
65134 )
66135
67- return reactError !== null ? fallback : children
136+ return reactError !== null ? (
137+ fallback
138+ ) : (
139+ < >
140+ < ReplaySsrOnlyErrors onBlockingError = { this . openErrorOverlay } />
141+ { children }
142+ </ >
143+ )
68144 }
69145}
0 commit comments