diff --git a/packages/next/src/client/components/react-dev-overlay/app/app-dev-overlay.tsx b/packages/next/src/client/components/react-dev-overlay/app/app-dev-overlay.tsx index 80110ed2b045..cd283fc724d9 100644 --- a/packages/next/src/client/components/react-dev-overlay/app/app-dev-overlay.tsx +++ b/packages/next/src/client/components/react-dev-overlay/app/app-dev-overlay.tsx @@ -5,72 +5,10 @@ import { } from '../shared' import type { GlobalErrorComponent } from '../../global-error' -import { useCallback, useEffect } from 'react' +import { useCallback } from 'react' import { AppDevOverlayErrorBoundary } from './app-dev-overlay-error-boundary' import { FontStyles } from '../font/font-styles' import { DevOverlay } from '../ui/dev-overlay' -import { handleClientError } from '../../errors/use-error-handler' -import { isNextRouterError } from '../../is-next-router-error' -import { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants' - -function readSsrError(): (Error & { digest?: string }) | null { - if (typeof document === 'undefined') { - return null - } - - const ssrErrorTemplateTag = document.querySelector( - 'template[data-next-error-message]' - ) - if (ssrErrorTemplateTag) { - const message: string = ssrErrorTemplateTag.getAttribute( - 'data-next-error-message' - )! - const stack = ssrErrorTemplateTag.getAttribute('data-next-error-stack') - const digest = ssrErrorTemplateTag.getAttribute('data-next-error-digest') - const error = new Error(message) - if (digest) { - ;(error as any).digest = digest - } - // Skip Next.js SSR'd internal errors that which will be handled by the error boundaries. - if (isNextRouterError(error)) { - return null - } - error.stack = stack || '' - return error - } - - return null -} - -// Needs to be in the same error boundary as the shell. -// If it commits, we know we recovered from an SSR error. -// If it doesn't commit, we errored again and React will take care of error reporting. -function ReplaySsrOnlyErrors({ - onBlockingError, -}: { - onBlockingError: () => void -}) { - if (process.env.NODE_ENV !== 'production') { - // Need to read during render. The attributes will be gone after commit. - const ssrError = readSsrError() - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(() => { - if (ssrError !== null) { - // TODO(veil): Include original Owner Stack (NDX-905) - // TODO(veil): Mark as recoverable error - // TODO(veil): console.error - handleClientError(ssrError) - - // If it's missing root tags, we can't recover, make it blocking. - if (ssrError.digest === MISSING_ROOT_TAGS_ERROR) { - onBlockingError() - } - } - }, [ssrError, onBlockingError]) - } - - return null -} function getSquashedHydrationErrorDetails() { // We don't squash hydration errors in the App Router. @@ -98,7 +36,6 @@ export function AppDevOverlay({ globalError={globalError} onError={openOverlay} > - {children} <> diff --git a/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx b/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx index f8d2de191e1f..ad2778cca125 100644 --- a/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx +++ b/packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx @@ -11,6 +11,7 @@ import { ACTION_BUILD_OK, ACTION_DEBUG_INFO, ACTION_DEV_INDICATOR, + ACTION_ERROR_OVERLAY_OPEN, ACTION_REFRESH, ACTION_STATIC_INDICATOR, ACTION_UNHANDLED_ERROR, @@ -21,6 +22,7 @@ import { useErrorOverlayReducer, } from '../shared' import { AppDevOverlay } from './app-dev-overlay' +import { ReplaySsrOnlyErrors } from './replay-ssr-only-errors' import { useErrorHandler } from '../../errors/use-error-handler' import { RuntimeErrorHandler } from '../../errors/runtime-error-handler' import { @@ -58,6 +60,7 @@ export interface Dispatcher { onDevIndicator(devIndicator: DevIndicatorServerState): void onUnhandledError(error: Error): void onUnhandledRejection(error: Error): void + openErrorOverlay(): void } let mostRecentCompilationHash: any = null @@ -529,6 +532,9 @@ export default function HotReload({ reason: error, }) }, + openErrorOverlay() { + dispatch({ type: ACTION_ERROR_OVERLAY_OPEN }) + }, } }, [dispatch]) @@ -618,6 +624,7 @@ export default function HotReload({ return ( + {children} ) diff --git a/packages/next/src/client/components/react-dev-overlay/app/replay-ssr-only-errors.tsx b/packages/next/src/client/components/react-dev-overlay/app/replay-ssr-only-errors.tsx new file mode 100644 index 000000000000..bc7196e89b3b --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/app/replay-ssr-only-errors.tsx @@ -0,0 +1,65 @@ +import { useEffect } from 'react' +import { handleClientError } from '../../errors/use-error-handler' +import { isNextRouterError } from '../../is-next-router-error' +import { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants' + +function readSsrError(): (Error & { digest?: string }) | null { + if (typeof document === 'undefined') { + return null + } + + const ssrErrorTemplateTag = document.querySelector( + 'template[data-next-error-message]' + ) + if (ssrErrorTemplateTag) { + const message: string = ssrErrorTemplateTag.getAttribute( + 'data-next-error-message' + )! + const stack = ssrErrorTemplateTag.getAttribute('data-next-error-stack') + const digest = ssrErrorTemplateTag.getAttribute('data-next-error-digest') + const error = new Error(message) + if (digest) { + ;(error as any).digest = digest + } + // Skip Next.js SSR'd internal errors that which will be handled by the error boundaries. + if (isNextRouterError(error)) { + return null + } + error.stack = stack || '' + return error + } + + return null +} + +/** + * Needs to be in the same error boundary as the shell. + * If it commits, we know we recovered from an SSR error. + * If it doesn't commit, we errored again and React will take care of error reporting. + */ +export function ReplaySsrOnlyErrors({ + onBlockingError, +}: { + onBlockingError: () => void +}) { + if (process.env.NODE_ENV !== 'production') { + // Need to read during render. The attributes will be gone after commit. + const ssrError = readSsrError() + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if (ssrError !== null) { + // TODO(veil): Include original Owner Stack (NDX-905) + // TODO(veil): Mark as recoverable error + // TODO(veil): console.error + handleClientError(ssrError) + + // If it's missing root tags, we can't recover, make it blocking. + if (ssrError.digest === MISSING_ROOT_TAGS_ERROR) { + onBlockingError() + } + } + }, [ssrError, onBlockingError]) + } + + return null +}