Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure bail out on ssr error in static generation #68764

Merged
merged 14 commits into from
Aug 13, 2024
15 changes: 8 additions & 7 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1223,20 +1223,21 @@ async function renderToStream(
renderOpts.page
)

const reactServerErrorsByDigest: Map<string, DigestedError> = new Map()
// Including both RSC errors and SSR errors
const renderErrorsByDigest: Map<string, DigestedError> = new Map()
const silenceLogger = false
const serverComponentsErrorHandler = createHTMLReactServerErrorHandler(
!!renderOpts.dev,
!!renderOpts.nextExport,
reactServerErrorsByDigest,
renderErrorsByDigest,
silenceLogger,
// RSC rendering error will report as SSR error
// @TODO we should report RSC errors where they happen for instrumentation purposes
// and should omit the error reporter in the SSR layer instead
undefined
)
function onServerRenderError(err: DigestedError) {
const renderSource = reactServerErrorsByDigest.has(err.digest)
function onServerRenderError(err: DigestedError, isRSCError: boolean) {
const renderSource = isRSCError
huozhi marked this conversation as resolved.
Show resolved Hide resolved
? 'react-server-components'
: 'server-rendering'
return renderOpts.onInstrumentationRequestError?.(
Expand All @@ -1249,7 +1250,7 @@ async function renderToStream(
const htmlRendererErrorHandler = createHTMLErrorHandler(
!!renderOpts.dev,
!!renderOpts.nextExport,
reactServerErrorsByDigest,
renderErrorsByDigest,
allCapturedErrors,
silenceLogger,
onServerRenderError
Expand Down Expand Up @@ -1645,8 +1646,8 @@ async function prerenderToStream(
// and should omit the error reporter in the SSR layer instead
undefined
)
function onServerRenderError(err: DigestedError) {
const renderSource = reactServerErrorsByDigest.has(err.digest)
function onServerRenderError(err: DigestedError, isRSCError: boolean) {
const renderSource = isRSCError
? 'react-server-components'
: 'server-rendering'
return renderOpts.onInstrumentationRequestError?.(
Expand Down
16 changes: 9 additions & 7 deletions packages/next/src/server/app-render/create-error-handler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ export function createHTMLErrorHandler(
dev: boolean,
isNextExport: boolean,
reactServerErrors: Map<string, DigestedError>,
allCapturedError: Array<unknown>,
allCapturedErrors: Array<unknown>,
silenceLogger: boolean,
onHTMLRenderError: (err: any) => void
onHTMLRenderError: (err: any, isRSCError: boolean) => void
huozhi marked this conversation as resolved.
Show resolved Hide resolved
): ErrorHandler {
return (err: any, errorInfo: any) => {
let isRSCError = false
// If the error already has a digest, respect the original digest,
// so it won't get re-generated into another new error.

Expand All @@ -152,17 +153,15 @@ export function createHTMLErrorHandler(
// This error is likely an obfuscated error from react-server.
// We recover the original error here.
err = reactServerErrors.get(err.digest)
} else {
// The error is not from react-server but has a digest
// from other means so we don't need to produce a new one
isRSCError = true
}
} else {
err.digest = stringHash(
err.message + (errorInfo?.stack || err.stack || '')
).toString()
}

allCapturedError.push(err)
allCapturedErrors.push(err)

// If the response was closed, we don't need to log the error.
if (isAbortError(err)) return
Expand All @@ -173,6 +172,9 @@ export function createHTMLErrorHandler(
// If this is a navigation error, we don't need to log the error.
if (isNextRouterError(err)) return err.digest

// In SSR rendering, we always collect the error
reactServerErrors.set(err.digest, err)
huozhi marked this conversation as resolved.
Show resolved Hide resolved

// If this error occurs, we know that we should be stopping the static
// render. This is only thrown in static generation when PPR is not enabled,
// which causes the whole page to be marked as dynamic. We don't need to
Expand Down Expand Up @@ -204,7 +206,7 @@ export function createHTMLErrorHandler(
}

if (!silenceLogger) {
onHTMLRenderError(err)
onHTMLRenderError(err, isRSCError)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Root({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
5 changes: 5 additions & 0 deletions test/production/app-dir/client-page-error-bailout/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use client'

export default function Page() {
throw new Error('client-page-error')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { nextTestSetup } from 'e2e-utils'

describe('app-dir - client-page-error-bailout', () => {
const { next, skipped } = nextTestSetup({
files: __dirname,
skipStart: true,
})

if (skipped) {
return
}

let stderr = ''
beforeAll(() => {
const onLog = (log: string) => {
stderr += log
}

next.on('stderr', onLog)
})

it('should bail out in static generation build', async () => {
await next.build()
expect(stderr).toContain(
'Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error'
)
expect(stderr).toContain('Error: client-page-error')
})
})
Loading