diff --git a/packages/next/src/lib/format-server-error.ts b/packages/next/src/lib/format-server-error.ts index 6723b9125d11e..3604ad67133ae 100644 --- a/packages/next/src/lib/format-server-error.ts +++ b/packages/next/src/lib/format-server-error.ts @@ -22,6 +22,22 @@ function setMessage(error: Error, message: string): void { } } +/** + * Input: + * Error: Something went wrong + at funcName (/path/to/file.js:10:5) + at anotherFunc (/path/to/file.js:15:10) + + * Output: + at funcName (/path/to/file.js:10:5) + at anotherFunc (/path/to/file.js:15:10) + */ +export function getStackWithoutErrorMessage(error: Error): string { + const stack = error.stack + if (!stack) return '' + return stack.replace(/^[^\n]*\n/, '') +} + export function formatServerError(error: Error): void { if (typeof error?.message !== 'string') return diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 7642827f4f4ac..2ee8dd6259d9c 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -81,6 +81,7 @@ import { isDynamicServerError } from '../../client/components/hooks-server-conte import { useFlightResponse } from './use-flight-response' import { isStaticGenBailoutError } from '../../client/components/static-generation-bailout' import { isInterceptionRouteAppPath } from '../future/helpers/interception-routes' +import { getStackWithoutErrorMessage } from '../../lib/format-server-error' export type GetDynamicParamFromSegment = ( // [slug] / [[slug]] / [...slug] @@ -1014,8 +1015,9 @@ async function renderToHTMLOrFlightImpl( console.log() if (renderOpts.experimental.missingSuspenseWithCSRBailout) { + const stack = getStackWithoutErrorMessage(err) error( - `${err.reason} should be wrapped in a suspense boundary at page "${pagePath}". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout` + `${err.reason} should be wrapped in a suspense boundary at page "${pagePath}". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n${stack}` ) throw err diff --git a/packages/next/src/server/app-render/create-error-handler.tsx b/packages/next/src/server/app-render/create-error-handler.tsx index fde69072ac555..88c717dea9c07 100644 --- a/packages/next/src/server/app-render/create-error-handler.tsx +++ b/packages/next/src/server/app-render/create-error-handler.tsx @@ -77,8 +77,7 @@ export function createErrorHandler({ const { logAppDirError } = require('../dev/log-app-dir-error') as typeof import('../dev/log-app-dir-error') logAppDirError(err) - } - if (process.env.NODE_ENV === 'production') { + } else { console.error(err) } } diff --git a/test/e2e/app-dir/missing-suspense-with-csr-bailout/app/layout-no-suspense.js b/test/e2e/app-dir/missing-suspense-with-csr-bailout/app/layout-no-suspense.js deleted file mode 100644 index f3791f288f9d7..0000000000000 --- a/test/e2e/app-dir/missing-suspense-with-csr-bailout/app/layout-no-suspense.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function Layout({ children }) { - return ( - -
{children} - - ) -} diff --git a/test/e2e/app-dir/missing-suspense-with-csr-bailout/app/layout-suspense.js b/test/e2e/app-dir/missing-suspense-with-csr-bailout/app/layout-suspense.js new file mode 100644 index 0000000000000..6e39f296f6568 --- /dev/null +++ b/test/e2e/app-dir/missing-suspense-with-csr-bailout/app/layout-suspense.js @@ -0,0 +1,12 @@ +import { Suspense } from 'react' + +export default function Layout({ children }) { + return ( + + +