Skip to content

Commit cdc2669

Browse files
committed
fix: detect loop in client error page
1 parent 6f42096 commit cdc2669

File tree

5 files changed

+68
-0
lines changed

5 files changed

+68
-0
lines changed

packages/next/client/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,14 @@ export function renderError(renderErrorProps: RenderErrorProps): Promise<any> {
467467
return pageLoader
468468
.loadPage('/_error')
469469
.then(({ page: ErrorComponent, styleSheets }) => {
470+
return lastAppProps?.Component === ErrorComponent
471+
? import('next/dist/pages/_error').then((m) => ({
472+
ErrorComponent: m.default as React.ComponentType<{}>,
473+
styleSheets: [],
474+
}))
475+
: { ErrorComponent, styleSheets }
476+
})
477+
.then(({ ErrorComponent, styleSheets }) => {
470478
// In production we do a normal render with the `ErrorComponent` as component.
471479
// If we've gotten here upon initial render, we can use the props from the server.
472480
// Otherwise, we need to call `getInitialProps` on `App` before mounting.

packages/next/types/misc.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ declare module 'next/dist/compiled/arg/index.js' {
4545
export = arg
4646
}
4747

48+
declare module 'next/dist/pages/_error'
49+
4850
declare module 'next/dist/compiled/babel/code-frame' {
4951
export * from '@babel/code-frame'
5052
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* eslint-disable no-unused-expressions, no-undef */
2+
let renderCount = 0
3+
4+
export default function Error() {
5+
renderCount++
6+
7+
// Guard to avoid endless loop crashing the browser tab.
8+
if (typeof window !== 'undefined' && renderCount < 3) {
9+
throw new Error('crash')
10+
}
11+
return `error threw ${renderCount} times`
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* eslint-disable no-unused-expressions, no-unused-vars */
2+
import React from 'react'
3+
import Link from 'next/link'
4+
5+
function page() {
6+
return (
7+
<Link href="/">
8+
<a id="nav">Client side nav</a>
9+
</Link>
10+
)
11+
}
12+
13+
page.getInitialProps = () => {
14+
if (typeof window !== 'undefined') {
15+
throw new Error('Oops from Home')
16+
}
17+
return {}
18+
}
19+
20+
export default page
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/* eslint-env jest */
2+
3+
import { join } from 'path'
4+
import webdriver from 'next-webdriver'
5+
import { nextBuild, nextStart, findPort, killApp } from 'next-test-utils'
6+
7+
jest.setTimeout(1000 * 60 * 1)
8+
9+
const appDir = join(__dirname, '..')
10+
const navSel = '#nav'
11+
const errorMessage = 'Application error: a client-side exception has occurred'
12+
13+
describe('Custom error page exception', () => {
14+
it('should handle errors from _error render', async () => {
15+
const { code } = await nextBuild(appDir)
16+
const appPort = await findPort()
17+
const app = await nextStart(appDir, appPort)
18+
const browser = await webdriver(appPort, '/')
19+
await browser.waitForElementByCss(navSel).elementByCss(navSel).click()
20+
const text = await (await browser.elementByCss('#__next')).text()
21+
killApp(app)
22+
23+
expect(code).toBe(0)
24+
expect(text).toMatch(errorMessage)
25+
})
26+
})

0 commit comments

Comments
 (0)