diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 949f254d991bf..150efb0994e71 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -728,6 +728,9 @@ async function renderToHTMLOrFlightImpl( } const is404 = res.statusCode === 404 + if (!is404 && !hasRedirectError) { + res.statusCode = 500 + } // Preserve the existing RSC inline chunks from the page rendering. // To avoid the same stream being operated twice, clone the origin stream for error rendering. diff --git a/test/e2e/app-dir/app/app/(newroot)/dashboard/project/[projectId]/page.js b/test/e2e/app-dir/app/app/(newroot)/dashboard/project/[projectId]/page.js index a1b399f433b04..a65cd9ed94013 100644 --- a/test/e2e/app-dir/app/app/(newroot)/dashboard/project/[projectId]/page.js +++ b/test/e2e/app-dir/app/app/(newroot)/dashboard/project/[projectId]/page.js @@ -1,6 +1,6 @@ import { use } from 'react' -function getData({ params }) { +async function getData({ params }) { return { now: Date.now(), params, diff --git a/test/production/app-dir/unexpected-error/app/isr-unexpected-error/page.tsx b/test/production/app-dir/unexpected-error/app/isr-unexpected-error/page.tsx new file mode 100644 index 0000000000000..f0b8eadf5127e --- /dev/null +++ b/test/production/app-dir/unexpected-error/app/isr-unexpected-error/page.tsx @@ -0,0 +1,9 @@ +export const revalidate = 1 + +export default function UnexpectedErrorPage(props) { + // use query param to only throw error during runtime, not build time + if (props.searchParams.error) { + throw new Error('Oh no') + } + return

/unexpected-error

+} diff --git a/test/production/app-dir/unexpected-error/app/layout.tsx b/test/production/app-dir/unexpected-error/app/layout.tsx new file mode 100644 index 0000000000000..e7077399c03ce --- /dev/null +++ b/test/production/app-dir/unexpected-error/app/layout.tsx @@ -0,0 +1,7 @@ +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/production/app-dir/unexpected-error/app/ssr-unexpected-error-after-streaming/loading.tsx b/test/production/app-dir/unexpected-error/app/ssr-unexpected-error-after-streaming/loading.tsx new file mode 100644 index 0000000000000..f39d065ff791e --- /dev/null +++ b/test/production/app-dir/unexpected-error/app/ssr-unexpected-error-after-streaming/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return

loading

+} diff --git a/test/production/app-dir/unexpected-error/app/ssr-unexpected-error-after-streaming/page.tsx b/test/production/app-dir/unexpected-error/app/ssr-unexpected-error-after-streaming/page.tsx new file mode 100644 index 0000000000000..615a7bfb5bc8d --- /dev/null +++ b/test/production/app-dir/unexpected-error/app/ssr-unexpected-error-after-streaming/page.tsx @@ -0,0 +1,7 @@ +export default function UnexpectedErrorPage(props) { + // use query param to only throw error during runtime, not build time + if (props.searchParams.error) { + throw new Error('Oh no') + } + return

/unexpected-error

+} diff --git a/test/production/app-dir/unexpected-error/app/ssr-unexpected-error/page.tsx b/test/production/app-dir/unexpected-error/app/ssr-unexpected-error/page.tsx new file mode 100644 index 0000000000000..615a7bfb5bc8d --- /dev/null +++ b/test/production/app-dir/unexpected-error/app/ssr-unexpected-error/page.tsx @@ -0,0 +1,7 @@ +export default function UnexpectedErrorPage(props) { + // use query param to only throw error during runtime, not build time + if (props.searchParams.error) { + throw new Error('Oh no') + } + return

/unexpected-error

+} diff --git a/test/production/app-dir/unexpected-error/next.config.js b/test/production/app-dir/unexpected-error/next.config.js new file mode 100644 index 0000000000000..807126e4cf0bf --- /dev/null +++ b/test/production/app-dir/unexpected-error/next.config.js @@ -0,0 +1,6 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/test/production/app-dir/unexpected-error/unexpected-error.test.ts b/test/production/app-dir/unexpected-error/unexpected-error.test.ts new file mode 100644 index 0000000000000..7a293c3aff010 --- /dev/null +++ b/test/production/app-dir/unexpected-error/unexpected-error.test.ts @@ -0,0 +1,26 @@ +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'unexpected-error', + { + files: __dirname, + }, + ({ next }) => { + it('should set response status to 500 for unexpected errors in ssr app route', async () => { + const res = await next.fetch('/ssr-unexpected-error?error=true') + expect(res.status).toBe(500) + }) + + it('cannot change response status when streaming has started', async () => { + const res = await next.fetch( + '/ssr-unexpected-error-after-streaming?error=true' + ) + expect(res.status).toBe(200) + }) + + it('should set response status to 500 for unexpected errors in isr app route', async () => { + const res = await next.fetch('/isr-unexpected-error?error=true') + expect(res.status).toBe(500) + }) + } +)