diff --git a/packages/next/src/server/route-modules/pages/pages-handler.ts b/packages/next/src/server/route-modules/pages/pages-handler.ts index 22e527c7c7db73..68bfabccfc16f8 100644 --- a/packages/next/src/server/route-modules/pages/pages-handler.ts +++ b/packages/next/src/server/route-modules/pages/pages-handler.ts @@ -693,11 +693,10 @@ export const getHandler = ({ } // when invoking _error before pages/500 we don't actually - // send the _error response - if ( - getRequestMeta(req, 'customErrorRender') || - (isErrorPage && isMinimalMode && res.statusCode === 500) - ) { + // send the _error response - only skip if customErrorRender is set + // (meaning pages/500 exists and will be rendered next), or if using + // the builtin _error in minimalMode to allow error bubbling + if (getRequestMeta(req, 'customErrorRender')) { return null } diff --git a/test/production/pages-get-initial-props-error/next.config.js b/test/production/pages-get-initial-props-error/next.config.js new file mode 100644 index 00000000000000..807126e4cf0bf5 --- /dev/null +++ b/test/production/pages-get-initial-props-error/next.config.js @@ -0,0 +1,6 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/test/production/pages-get-initial-props-error/pages-get-initial-props-error.test.ts b/test/production/pages-get-initial-props-error/pages-get-initial-props-error.test.ts new file mode 100644 index 00000000000000..1e6774c3ab58ea --- /dev/null +++ b/test/production/pages-get-initial-props-error/pages-get-initial-props-error.test.ts @@ -0,0 +1,17 @@ +import { nextTestSetup } from 'e2e-utils' + +describe('pages-get-initial-props-error', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should render _error with 500 status code when getInitialProps throws', async () => { + const browser = await next.browser('/gip-error') + expect(await browser.elementByCss('p').text()).toBe( + 'An error 500 occurred on server' + ) + + const response = await next.fetch('/gip-error') + expect(response.status).toBe(500) + }) +}) diff --git a/test/production/pages-get-initial-props-error/pages/_app.tsx b/test/production/pages-get-initial-props-error/pages/_app.tsx new file mode 100644 index 00000000000000..93928c3da70e23 --- /dev/null +++ b/test/production/pages-get-initial-props-error/pages/_app.tsx @@ -0,0 +1,5 @@ +import type { AppProps } from 'next/app' + +export default function App({ Component, pageProps }: AppProps) { + return +} diff --git a/test/production/pages-get-initial-props-error/pages/_document.tsx b/test/production/pages-get-initial-props-error/pages/_document.tsx new file mode 100644 index 00000000000000..bff2b1b2821cb2 --- /dev/null +++ b/test/production/pages-get-initial-props-error/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/test/production/pages-get-initial-props-error/pages/_error.tsx b/test/production/pages-get-initial-props-error/pages/_error.tsx new file mode 100644 index 00000000000000..e281c1e1a3ce21 --- /dev/null +++ b/test/production/pages-get-initial-props-error/pages/_error.tsx @@ -0,0 +1,38 @@ +import type { NextPageContext } from 'next' + +type ErrorProps = { + statusCode?: number +} + +function ErrorPage({ statusCode }: ErrorProps) { + return ( +

+ {statusCode !== undefined + ? `An error ${statusCode} occurred on server` + : 'An error occurred on client'} +

+ ) +} + +ErrorPage.getInitialProps = ({ res, err }: NextPageContext) => { + console.log('ErrorPage.gip:err', err, '!!res', !!res) + let statusCode = 200 + try { + statusCode = res?.statusCode ?? err?.statusCode ?? (res || err ? 500 : 404) + } catch (error) { + console.error('ErrorPage.gip:try-catch', error) + } + + console.trace( + 'ErrorPage.status code', + 'res?.statusCode', + res?.statusCode, + 'err?.statusCode', + err?.statusCode, + 'final', + statusCode + ) + return { statusCode } +} + +export default ErrorPage diff --git a/test/production/pages-get-initial-props-error/pages/gip-error.tsx b/test/production/pages-get-initial-props-error/pages/gip-error.tsx new file mode 100644 index 00000000000000..24137289269bc4 --- /dev/null +++ b/test/production/pages-get-initial-props-error/pages/gip-error.tsx @@ -0,0 +1,21 @@ +import type { GetServerSideProps } from 'next' + +class IntentionalServerError extends Error { + statusCode: number + + constructor(message: string, statusCode = 500) { + super(message) + this.name = 'IntentionalServerError' + this.statusCode = statusCode + } +} + +export default function ErrorDemo() { + return

Custom Error Page Demo

+} + +export const getServerSideProps: GetServerSideProps = async () => { + throw new IntentionalServerError( + 'Intentional error triggered via getServerSideProps.' + ) +} diff --git a/test/production/required-server-files-ssr-404/test/index.test.ts b/test/production/required-server-files-ssr-404/test/index.test.ts index 56be852c7420c4..75290c1e4e1d6f 100644 --- a/test/production/required-server-files-ssr-404/test/index.test.ts +++ b/test/production/required-server-files-ssr-404/test/index.test.ts @@ -551,19 +551,31 @@ describe('Required Server Files', () => { it('should bubble error correctly for gip page', async () => { const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' }) expect(res.status).toBe(500) - expect(await res.text()).toBe('Internal Server Error') + const body = await res.text() + const $ = cheerio.load(body) + + expect($('h1').text()).toBe('500') + expect($('h2').text()).toBe('Internal Server Error.') }) it('should bubble error correctly for gssp page', async () => { const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' }) expect(res.status).toBe(500) - expect(await res.text()).toBe('Internal Server Error') + const body = await res.text() + const $ = cheerio.load(body) + + expect($('h1').text()).toBe('500') + expect($('h2').text()).toBe('Internal Server Error.') }) it('should bubble error correctly for gsp page', async () => { const res = await fetchViaHTTP(appPort, '/errors/gsp/crash') expect(res.status).toBe(500) - expect(await res.text()).toBe('Internal Server Error') + const body = await res.text() + const $ = cheerio.load(body) + + expect($('h1').text()).toBe('500') + expect($('h2').text()).toBe('Internal Server Error.') }) it('should normalize optional values correctly for SSP page', async () => { diff --git a/test/production/standalone-mode/required-server-files/required-server-files.test.ts b/test/production/standalone-mode/required-server-files/required-server-files.test.ts index 3a2ce8fffdd3dd..246619f210dd0e 100644 --- a/test/production/standalone-mode/required-server-files/required-server-files.test.ts +++ b/test/production/standalone-mode/required-server-files/required-server-files.test.ts @@ -1060,7 +1060,10 @@ describe('required server files', () => { it('should bubble error correctly for gip page', async () => { const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' }) expect(res.status).toBe(500) - expect(await res.text()).toBe('Internal Server Error') + const body = await res.text() + const $ = cheerio.load(body) + expect($('h1').text()).toBe('500') + expect($('h2').text()).toBe('Internal Server Error.') await retry(() => { expect(errors.join('\n')).toInclude('gip hit an oops') @@ -1070,7 +1073,10 @@ describe('required server files', () => { it('should bubble error correctly for gssp page', async () => { const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' }) expect(res.status).toBe(500) - expect(await res.text()).toBe('Internal Server Error') + const body = await res.text() + const $ = cheerio.load(body) + expect($('h1').text()).toBe('500') + expect($('h2').text()).toBe('Internal Server Error.') await retry(() => { expect(errors.join('\n')).toInclude('gssp hit an oops') @@ -1080,7 +1086,10 @@ describe('required server files', () => { it('should bubble error correctly for gsp page', async () => { const res = await fetchViaHTTP(appPort, '/errors/gsp/crash') expect(res.status).toBe(500) - expect(await res.text()).toBe('Internal Server Error') + const body = await res.text() + const $ = cheerio.load(body) + expect($('h1').text()).toBe('500') + expect($('h2').text()).toBe('Internal Server Error.') await retry(() => { expect(errors.join('\n')).toInclude('gsp hit an oops')