Skip to content
Closed
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
9 changes: 4 additions & 5 deletions packages/next/src/server/route-modules/pages/pages-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
6 changes: 6 additions & 0 deletions test/production/pages-get-initial-props-error/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {}

module.exports = nextConfig
Original file line number Diff line number Diff line change
@@ -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)
})
})
5 changes: 5 additions & 0 deletions test/production/pages-get-initial-props-error/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
13 changes: 13 additions & 0 deletions test/production/pages-get-initial-props-error/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
38 changes: 38 additions & 0 deletions test/production/pages-get-initial-props-error/pages/_error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { NextPageContext } from 'next'

type ErrorProps = {
statusCode?: number
}

function ErrorPage({ statusCode }: ErrorProps) {
return (
<p>
{statusCode !== undefined
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</p>
)
}

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
21 changes: 21 additions & 0 deletions test/production/pages-get-initial-props-error/pages/gip-error.tsx
Original file line number Diff line number Diff line change
@@ -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 <h1>Custom Error Page Demo</h1>
}

export const getServerSideProps: GetServerSideProps = async () => {
throw new IntentionalServerError(
'Intentional error triggered via getServerSideProps.'
)
}
18 changes: 15 additions & 3 deletions test/production/required-server-files-ssr-404/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand All @@ -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.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels off, when we are in minimal mode we don't actually return the rendered content we only invoke _error.getInitialProps if present to allow reporting the error but then we bubble the error to handle at the top-level. On Vercel we would crash the lambda and then we would serve the error content separately that is where the error content should actually be returned.


await retry(() => {
expect(errors.join('\n')).toInclude('gsp hit an oops')
Expand Down
Loading