Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -98,7 +36,6 @@ export function AppDevOverlay({
globalError={globalError}
onError={openOverlay}
>
<ReplaySsrOnlyErrors onBlockingError={openOverlay} />
{children}
</AppDevOverlayErrorBoundary>
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -529,6 +532,9 @@ export default function HotReload({
reason: error,
})
},
openErrorOverlay() {
dispatch({ type: ACTION_ERROR_OVERLAY_OPEN })
},
}
}, [dispatch])

Expand Down Expand Up @@ -618,6 +624,7 @@ export default function HotReload({

return (
<AppDevOverlay state={state} dispatch={dispatch} globalError={globalError}>
<ReplaySsrOnlyErrors onBlockingError={dispatcher.openErrorOverlay} />
{children}
</AppDevOverlay>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}